fighting the linux focus problem
[fedora-idea.git] / platform / platform-api / src / com / intellij / ui / tabs / impl / JBTabsImpl.java
blob4c1bebd02157724ea62e5f2cd687a8dc78f59ec0
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ui.tabs.impl;
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.actionSystem.*;
20 import com.intellij.openapi.application.ModalityState;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.ui.ShadowAction;
23 import com.intellij.openapi.ui.TestableUi;
24 import com.intellij.openapi.util.*;
25 import com.intellij.openapi.util.registry.Registry;
26 import com.intellij.openapi.wm.*;
27 import com.intellij.openapi.wm.impl.content.GraphicsConfig;
28 import com.intellij.ui.CaptionPanel;
29 import com.intellij.ui.tabs.*;
30 import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout;
31 import com.intellij.ui.tabs.impl.singleRow.SingleRowPassInfo;
32 import com.intellij.ui.tabs.impl.table.TableLayout;
33 import com.intellij.ui.tabs.impl.table.TablePassInfo;
34 import com.intellij.util.ui.Animator;
35 import com.intellij.util.ui.TimedDeadzone;
36 import com.intellij.util.ui.UIUtil;
37 import com.intellij.util.ui.update.LazyUiDisposable;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
42 import javax.swing.*;
43 import javax.swing.event.PopupMenuEvent;
44 import javax.swing.event.PopupMenuListener;
45 import javax.swing.plaf.ComponentUI;
46 import java.awt.*;
47 import java.awt.event.*;
48 import java.awt.geom.Line2D;
49 import java.awt.image.BufferedImage;
50 import java.beans.PropertyChangeEvent;
51 import java.beans.PropertyChangeListener;
52 import java.util.*;
53 import java.util.List;
55 public class JBTabsImpl extends JComponent
56 implements JBTabs, PropertyChangeListener, TimerListener, DataProvider, PopupMenuListener, Disposable, JBTabsPresentation, TestableUi {
58 static DataKey<JBTabsImpl> NAVIGATION_ACTIONS_KEY = DataKey.create("JBTabs");
60 ActionManager myActionManager;
61 public final List<TabInfo> myVisibleInfos = new ArrayList<TabInfo>();
62 private final Map<TabInfo, Integer> myHiddenInfos = new HashMap<TabInfo, Integer>();
64 private TabInfo mySelectedInfo;
65 public final Map<TabInfo, TabLabel> myInfo2Label = new HashMap<TabInfo, TabLabel>();
66 public final Map<TabInfo, Toolbar> myInfo2Toolbar = new HashMap<TabInfo, Toolbar>();
67 public Dimension myHeaderFitSize;
69 private Insets myInnerInsets = new Insets(0, 0, 0, 0);
71 private final List<EventListener> myTabMouseListeners = new ArrayList<EventListener>();
72 private final List<TabsListener> myTabListeners = new ArrayList<TabsListener>();
73 public boolean myFocused;
75 private Getter<ActionGroup> myPopupGroup;
76 private String myPopupPlace;
78 TabInfo myPopupInfo;
79 DefaultActionGroup myNavigationActions;
81 PopupMenuListener myPopupListener;
82 JPopupMenu myActivePopup;
84 public boolean myHorizontalSide = true;
86 private boolean myStealthTabMode = false;
88 private DataProvider myDataProvider;
90 private final WeakHashMap<Component, Component> myDeferredToRemove = new WeakHashMap<Component, Component>();
92 private final SingleRowLayout mySingleRowLayout = new SingleRowLayout(this);
93 private final TableLayout myTableLayout = new TableLayout(this);
96 private TabLayout myLayout = mySingleRowLayout;
97 LayoutPassInfo myLastLayoutPass;
98 private TabInfo myLastPaintedSelection;
100 public boolean myForcedRelayout;
102 private UiDecorator myUiDecorator;
103 static final UiDecorator ourDefaultDecorator = new DefautDecorator();
105 private boolean myPaintFocus;
107 private boolean myHideTabs = false;
108 @Nullable private Project myProject;
110 private boolean myRequestFocusOnLastFocusedComponent = false;
111 private boolean myListenerAdded;
112 final Set<TabInfo> myAttractions = new HashSet<TabInfo>();
113 private Animator myAnimator;
114 private List<TabInfo> myAllTabs;
115 private boolean myPaintBlocked;
116 private BufferedImage myImage;
117 private IdeFocusManager myFocusManager;
118 private boolean myAdjustBorders = true;
120 boolean myAddNavigationGroup = true;
122 private boolean myGhostsAlwaysVisible = false;
123 private boolean myDisposed;
124 private boolean myToDrawBorderIfTabsHidden = true;
125 private Color myActiveTabFillIn;
127 private boolean myTabLabelActionsAutoHide;
129 private final TabActionsAutoHideListener myTabActionsAutoHideListener = new TabActionsAutoHideListener();
130 private IdeGlassPane myGlassPane;
131 @NonNls private static final String LAYOUT_DONE = "Layout.done";
133 private TimedDeadzone.Length myTabActionsMouseDeadzone = TimedDeadzone.DEFAULT;
135 private long myRemoveDefferredRequest;
136 private boolean myTestMode;
138 private JBTabsPosition myPosition = JBTabsPosition.top;
140 private final TabsBorder myBorder = new TabsBorder(this);
141 private BaseNavigationAction myNextAction;
142 private BaseNavigationAction myPrevAction;
144 private boolean myWasEverShown;
146 private boolean myTabDraggingEnabled;
147 private DragHelper myDragHelper;
148 private boolean myNavigationActionsEnabled = true;
149 private boolean myUseBufferedPaint = true;
151 public JBTabsImpl(@NotNull Project project) {
152 this(project, project);
155 public JBTabsImpl(@NotNull Project project, @NotNull Disposable parent) {
156 this(project, ActionManager.getInstance(), IdeFocusManager.getInstance(project), parent);
159 public JBTabsImpl(@Nullable Project project, IdeFocusManager focusManager, @NotNull Disposable parent) {
160 this(project, ActionManager.getInstance(), focusManager, parent);
163 public JBTabsImpl(@Nullable Project project, ActionManager actionManager, IdeFocusManager focusManager, @NotNull Disposable parent) {
164 myProject = project;
165 myActionManager = actionManager;
166 myFocusManager = focusManager != null ? focusManager : IdeFocusManager.getGlobalInstance();
168 setOpaque(true);
169 setPaintBorder(-1, -1, -1, -1);
171 Disposer.register(parent, this);
173 myNavigationActions = new DefaultActionGroup();
175 if (myActionManager != null) {
176 myNextAction = new SelectNextAction(this, myActionManager);
177 myPrevAction = new SelectPreviousAction(this, myActionManager);
179 myNavigationActions.add(myNextAction);
180 myNavigationActions.add(myPrevAction);
183 setUiDecorator(null);
185 myPopupListener = new PopupMenuListener() {
186 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
189 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
190 disposePopupListener();
193 public void popupMenuCanceled(final PopupMenuEvent e) {
194 disposePopupListener();
198 addMouseListener(new MouseAdapter() {
199 public void mousePressed(final MouseEvent e) {
200 if (mySingleRowLayout.myLastSingRowLayout != null &&
201 mySingleRowLayout.myLastSingRowLayout.moreRect != null &&
202 mySingleRowLayout.myLastSingRowLayout.moreRect.contains(e.getPoint())) {
203 showMorePopup(e);
208 myAnimator = new Animator("JBTabs Attractions", 2, 500, true, 0, -1) {
209 public void paintNow(final float frame, final float totalFrames, final float cycle) {
210 repaintAttractions();
213 myAnimator.setTakInitialDelay(false);
215 setFocusCycleRoot(true);
216 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
217 public Component getDefaultComponent(final Container aContainer) {
218 return getToFocus();
222 add(mySingleRowLayout.myLeftGhost);
223 add(mySingleRowLayout.myRightGhost);
226 new LazyUiDisposable<JBTabsImpl>(parent, this, this, project) {
227 protected void initialize(@NotNull Disposable parent, @NotNull JBTabsImpl child, @Nullable Project project) {
228 myProject = project;
230 Disposer.register(child, myAnimator);
231 Disposer.register(child, new Disposable() {
232 public void dispose() {
233 removeTimerUpdate();
237 if (!myTestMode) {
238 final IdeGlassPane gp = IdeGlassPaneUtil.find(child);
239 if (gp != null) {
240 gp.addMouseMotionPreprocessor(myTabActionsAutoHideListener, child);
241 myGlassPane = gp;
244 UIUtil.addAwtListener(new AWTEventListener() {
245 public void eventDispatched(final AWTEvent event) {
246 if (mySingleRowLayout.myMorePopup != null) return;
247 processFocusChange();
249 }, AWTEvent.FOCUS_EVENT_MASK, child);
251 myDragHelper = new DragHelper(child);
252 myDragHelper.start();
255 if (myProject != null && myFocusManager == IdeFocusManager.getGlobalInstance()) {
256 myFocusManager = IdeFocusManager.getInstance(myProject);
263 public JBTabs setNavigationActiondBinding(String prevActionId, String nextActionId) {
264 if (myNextAction != null) {
265 myNextAction.reconnect(nextActionId);
267 if (myPrevAction != null) {
268 myPrevAction.reconnect(prevActionId);
271 return this;
274 public JBTabs setNavigationActionsEnabled(boolean enabled) {
275 myNavigationActionsEnabled = enabled;
276 return this;
279 public final boolean isDisposed() {
280 return myDisposed;
283 public void dispose() {
284 myDisposed = true;
285 mySelectedInfo = null;
286 resetTabsCache();
287 myAttractions.clear();
288 myVisibleInfos.clear();
289 myUiDecorator = null;
290 myImage = null;
291 myActivePopup = null;
292 myInfo2Label.clear();
293 myInfo2Toolbar.clear();
294 myTabListeners.clear();
297 void resetTabsCache() {
298 myAllTabs = null;
301 private void processFocusChange() {
302 Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
303 if (owner == null) {
304 setFocused(false);
305 return;
308 if (owner == this || SwingUtilities.isDescendingFrom(owner, this)) {
309 setFocused(true);
311 else {
312 setFocused(false);
316 private void repaintAttractions() {
317 boolean needsUpdate = false;
318 for (TabInfo each : myVisibleInfos) {
319 TabLabel eachLabel = myInfo2Label.get(each);
320 needsUpdate |= eachLabel.repaintAttraction();
323 if (needsUpdate) {
324 relayout(true, false);
328 public void addNotify() {
329 super.addNotify();
330 addTimerUpdate();
333 public void removeNotify() {
334 super.removeNotify();
336 setFocused(false);
338 removeTimerUpdate();
340 if (myGlassPane != null) {
341 myGlassPane.removeMouseMotionPreprocessor(myTabActionsAutoHideListener);
342 myGlassPane = null;
346 private void addTimerUpdate() {
347 if (myActionManager != null && !myListenerAdded) {
348 myActionManager.addTimerListener(500, this);
349 myListenerAdded = true;
353 private void removeTimerUpdate() {
354 if (myActionManager != null && myListenerAdded) {
355 myActionManager.removeTimerListener(this);
356 myListenerAdded = false;
360 void setTestMode(final boolean testMode) {
361 myTestMode = testMode;
364 public void layoutComp(SingleRowPassInfo data, int deltaX, int deltaY, int deltaWidth, int deltaHeight) {
365 if (data.hToolbar != null) {
366 final int toolbarHeight = data.hToolbar.getPreferredSize().height;
367 final Rectangle compRect = layoutComp(deltaX, toolbarHeight + deltaY, data.comp, deltaWidth, deltaHeight);
368 layout(data.hToolbar, compRect.x, compRect.y - toolbarHeight, compRect.width, toolbarHeight);
370 else if (data.vToolbar != null) {
371 final int toolbarWidth = data.vToolbar.getPreferredSize().width;
372 final Rectangle compRect = layoutComp(toolbarWidth + deltaX, deltaY, data.comp, deltaWidth, deltaHeight);
373 layout(data.vToolbar, compRect.x - toolbarWidth, compRect.y, toolbarWidth, compRect.height);
375 else {
376 layoutComp(deltaX, deltaY, data.comp, deltaWidth, deltaHeight);
380 class TabActionsAutoHideListener extends MouseMotionAdapter {
382 private TabLabel myCurrentOverLabel;
383 private Point myLastOverPoint;
385 @Override
386 public void mouseMoved(final MouseEvent e) {
387 if (!myTabLabelActionsAutoHide) return;
389 final Point point = SwingUtilities.convertPoint(e.getComponent(), e.getX(), e.getY(), JBTabsImpl.this);
390 myLastOverPoint = point;
391 processMouseOver();
394 void processMouseOver() {
395 if (!myTabLabelActionsAutoHide) return;
397 if (myLastOverPoint == null) return;
399 if (myLastOverPoint.x >= 0 && myLastOverPoint.x < getWidth() && myLastOverPoint.y > 0 && myLastOverPoint.y < getHeight()) {
400 final TabLabel label = myInfo2Label.get(_findInfo(myLastOverPoint, true));
401 if (label != null) {
402 if (myCurrentOverLabel != null) {
403 myCurrentOverLabel.toggleShowActions(false);
405 label.toggleShowActions(true);
406 myCurrentOverLabel = label;
407 return;
411 if (myCurrentOverLabel != null) {
412 myCurrentOverLabel.toggleShowActions(false);
413 myCurrentOverLabel = null;
419 public ModalityState getModalityState() {
420 return ModalityState.stateForComponent(this);
423 public void run() {
424 updateTabActions(false);
427 public void updateTabActions(final boolean validateNow) {
428 final Ref<Boolean> changed = new Ref<Boolean>(Boolean.FALSE);
429 for (final TabInfo eachInfo : myInfo2Label.keySet()) {
430 updateTab(new Runnable() {
431 public void run() {
432 final boolean changes = myInfo2Label.get(eachInfo).updateTabActions();
433 changed.set(changed.get().booleanValue() || changes);
435 }, eachInfo);
438 if (changed.get().booleanValue()) {
439 if (validateNow) {
440 validate();
441 paintImmediately(0, 0, getWidth(), getHeight());
446 private void showMorePopup(final MouseEvent e) {
447 mySingleRowLayout.myMorePopup = new JPopupMenu();
448 for (final TabInfo each : myVisibleInfos) {
449 final JCheckBoxMenuItem item = new JCheckBoxMenuItem(each.getText());
450 mySingleRowLayout.myMorePopup.add(item);
451 if (getSelectedInfo() == each) {
452 item.setSelected(true);
454 item.addActionListener(new ActionListener() {
455 public void actionPerformed(final ActionEvent e) {
456 select(each, true);
461 mySingleRowLayout.myMorePopup.addPopupMenuListener(new PopupMenuListener() {
462 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
465 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
466 mySingleRowLayout.myMorePopup = null;
469 public void popupMenuCanceled(final PopupMenuEvent e) {
470 mySingleRowLayout.myMorePopup = null;
474 mySingleRowLayout.myMorePopup.show(this, e.getX(), e.getY());
478 private JComponent getToFocus() {
479 final TabInfo info = getSelectedInfo();
481 if (info == null) return null;
483 JComponent toFocus = null;
485 if (isRequestFocusOnLastFocusedComponent() && info.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) {
486 toFocus = info.getLastFocusOwner();
489 if (toFocus == null && info.getPreferredFocusableComponent() == null) {
490 return null;
494 if (toFocus == null) {
495 toFocus = info.getPreferredFocusableComponent();
496 final JComponent policyToFocus = myFocusManager.getFocusTargetFor(toFocus);
497 if (policyToFocus != null) {
498 toFocus = policyToFocus;
502 return toFocus;
505 public void requestFocus() {
506 final JComponent toFocus = getToFocus();
507 if (toFocus != null) {
508 toFocus.requestFocus();
510 else {
511 super.requestFocus();
515 public boolean requestFocusInWindow() {
516 final JComponent toFocus = getToFocus();
517 if (toFocus != null) {
518 return toFocus.requestFocusInWindow();
520 else {
521 return super.requestFocusInWindow();
525 private JBTabsImpl findTabs(Component c) {
526 Component eachParent = c;
527 while (eachParent != null) {
528 if (eachParent instanceof JBTabsImpl) {
529 return (JBTabsImpl)eachParent;
531 eachParent = eachParent.getParent();
534 return null;
538 @NotNull
539 public TabInfo addTab(TabInfo info, int index) {
540 if (getTabs().contains(info)) {
541 return getTabs().get(getTabs().indexOf(info));
544 info.getChangeSupport().addPropertyChangeListener(this);
545 final TabLabel label = new TabLabel(this, info);
546 myInfo2Label.put(info, label);
548 if (index < 0) {
549 myVisibleInfos.add(info);
551 else if (index > myVisibleInfos.size() - 1) {
552 myVisibleInfos.add(info);
554 else {
555 myVisibleInfos.add(index, info);
558 resetTabsCache();
561 updateText(info);
562 updateIcon(info);
563 updateSideComponent(info);
564 updateTabActions(info);
566 add(label);
568 adjust(info);
570 updateAll(false, false);
572 if (info.isHidden()) {
573 updateHiding();
576 if (getTabCount() == 1) {
577 fireBeforeSelectionChanged(null, info);
578 fireSelectionChanged(null, info);
581 return info;
585 @NotNull
586 public TabInfo addTab(TabInfo info) {
587 return addTab(info, -1);
590 public ActionGroup getPopupGroup() {
591 return myPopupGroup != null ? myPopupGroup.get() : null;
594 public String getPopupPlace() {
595 return myPopupPlace;
598 public JBTabs setPopupGroup(@NotNull final ActionGroup popupGroup, @NotNull String place, final boolean addNavigationGroup) {
599 return setPopupGroup(new Getter<ActionGroup>() {
600 public ActionGroup get() {
601 return popupGroup;
603 }, place, addNavigationGroup);
606 public JBTabs setPopupGroup(@NotNull final Getter<ActionGroup> popupGroup,
607 @NotNull final String place,
608 final boolean addNavigationGroup) {
609 myPopupGroup = popupGroup;
610 myPopupPlace = place;
611 myAddNavigationGroup = addNavigationGroup;
612 return this;
615 private void updateAll(final boolean forcedRelayout, final boolean now) {
616 mySelectedInfo = getSelectedInfo();
617 updateContainer(forcedRelayout, now);
618 removeDeferred();
619 updateListeners();
620 updateTabActions(false);
621 updateEnabling();
624 private boolean isMyChildIsFocusedNow() {
625 final Component owner = getFocusOwner();
626 if (owner == null) return false;
629 if (mySelectedInfo != null) {
630 if (!SwingUtilities.isDescendingFrom(owner, mySelectedInfo.getComponent())) return false;
633 return SwingUtilities.isDescendingFrom(owner, this);
636 @Nullable
637 private static JComponent getFocusOwner() {
638 final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
639 return (JComponent)(owner instanceof JComponent ? owner : null);
642 public ActionCallback select(@NotNull TabInfo info, boolean requestFocus) {
643 return _setSelected(info, requestFocus);
646 private ActionCallback _setSelected(final TabInfo info, final boolean requestFocus) {
647 if (mySelectedInfo != null && mySelectedInfo.equals(info)) {
648 if (!requestFocus) {
649 return new ActionCallback.Done();
651 else {
652 return requestFocus(getToFocus());
656 if (myRequestFocusOnLastFocusedComponent && mySelectedInfo != null) {
657 if (isMyChildIsFocusedNow()) {
658 mySelectedInfo.setLastFocusOwner(getFocusOwner());
662 TabInfo oldInfo = mySelectedInfo;
663 mySelectedInfo = info;
664 final TabInfo newInfo = getSelectedInfo();
666 fireBeforeSelectionChanged(oldInfo, newInfo);
668 updateContainer(false, true);
670 fireSelectionChanged(oldInfo, newInfo);
672 if (requestFocus) {
673 final JComponent toFocus = getToFocus();
674 if (myProject != null && toFocus != null) {
675 final ActionCallback result = new ActionCallback();
676 requestFocus(toFocus).doWhenProcessed(new Runnable() {
677 public void run() {
678 if (myDisposed) {
679 result.setRejected();
681 else {
682 removeDeferred().notifyWhenDone(result);
686 return result;
688 else {
689 requestFocus();
690 return removeDeferred();
693 else {
694 return removeDeferred();
698 private void fireBeforeSelectionChanged(TabInfo oldInfo, TabInfo newInfo) {
699 if (oldInfo != newInfo) {
700 for (TabsListener eachListener : myTabListeners) {
701 eachListener.beforeSelectionChanged(oldInfo, newInfo);
706 private void fireSelectionChanged(TabInfo oldInfo, TabInfo newInfo) {
707 if (oldInfo != newInfo) {
708 for (TabsListener eachListener : myTabListeners) {
709 if (eachListener != null) {
710 eachListener.selectionChanged(oldInfo, newInfo);
716 private ActionCallback requestFocus(final JComponent toFocus) {
717 if (toFocus == null) return new ActionCallback.Done();
719 if (myTestMode) {
720 toFocus.requestFocus();
721 return new ActionCallback.Done();
724 return myFocusManager.requestFocus(new FocusCommand.ByComponent(toFocus), true);
727 private ActionCallback removeDeferred() {
728 final ActionCallback callback = new ActionCallback();
730 final long executionRequest = ++myRemoveDefferredRequest;
732 final Runnable onDone = new Runnable() {
733 public void run() {
734 if (myRemoveDefferredRequest == executionRequest) {
735 removeDeferredNow();
738 callback.setDone();
742 myFocusManager.doWhenFocusSettlesDown(onDone);
744 return callback;
747 private void queueForRemove(Component c) {
748 if (c instanceof JComponent) {
749 addToDeferredRemove(c);
751 else {
752 remove(c);
756 private void unqueueFromRemove(Component c) {
757 myDeferredToRemove.remove(c);
760 private void removeDeferredNow() {
761 for (Component each : myDeferredToRemove.keySet()) {
762 if (each != null && each.getParent() == this) {
763 remove(each);
766 myDeferredToRemove.clear();
769 private void printRemoveInfo(final Component each) {
770 TabInfo removingInfo = null;
771 final List<TabInfo> all = getTabs();
772 for (TabInfo eachInfo : all) {
773 if (eachInfo.getComponent() == each) {
774 removingInfo = eachInfo;
775 break;
779 //System.out.println(" - removing " + (removingInfo != null ? " component for " + removingInfo : each));
782 public void propertyChange(final PropertyChangeEvent evt) {
783 final TabInfo tabInfo = (TabInfo)evt.getSource();
784 if (TabInfo.ACTION_GROUP.equals(evt.getPropertyName())) {
785 updateSideComponent(tabInfo);
786 relayout(false, false);
788 else if (TabInfo.COMPONENT.equals(evt.getPropertyName())) {
789 relayout(true, false);
791 else if (TabInfo.TEXT.equals(evt.getPropertyName())) {
792 updateText(tabInfo);
794 else if (TabInfo.ICON.equals(evt.getPropertyName())) {
795 updateIcon(tabInfo);
797 else if (TabInfo.TAB_COLOR.equals(evt.getPropertyName())) {
798 updateColor(tabInfo);
800 else if (TabInfo.ALERT_STATUS.equals(evt.getPropertyName())) {
801 boolean start = ((Boolean)evt.getNewValue()).booleanValue();
802 updateAttraction(tabInfo, start);
804 else if (TabInfo.TAB_ACTION_GROUP.equals(evt.getPropertyName())) {
805 updateTabActions(tabInfo);
806 relayout(false, false);
808 else if (TabInfo.HIDDEN.equals(evt.getPropertyName())) {
809 updateHiding();
810 relayout(false, false);
812 else if (TabInfo.ENABLED.equals(evt.getPropertyName())) {
813 updateEnabling();
817 private void updateEnabling() {
818 final List<TabInfo> all = getTabs();
819 for (TabInfo each : all) {
820 final TabLabel eachLabel = myInfo2Label.get(each);
821 eachLabel.setTabEnabled(each.isEnabled());
824 final TabInfo selected = getSelectedInfo();
825 if (selected != null && !selected.isEnabled()) {
826 final TabInfo toSelect = getToSelectOnRemoveOf(selected);
827 if (toSelect != null) {
828 select(toSelect, myFocusManager.getFocusedDescendantFor(this) != null);
833 private void updateHiding() {
834 boolean update = false;
836 Iterator<TabInfo> visible = myVisibleInfos.iterator();
837 while (visible.hasNext()) {
838 TabInfo each = visible.next();
839 if (each.isHidden() && !myHiddenInfos.containsKey(each)) {
840 myHiddenInfos.put(each, myVisibleInfos.indexOf(each));
841 visible.remove();
842 update = true;
847 Iterator<TabInfo> hidden = myHiddenInfos.keySet().iterator();
848 while (hidden.hasNext()) {
849 TabInfo each = hidden.next();
850 if (!each.isHidden() && myHiddenInfos.containsKey(each)) {
851 myVisibleInfos.add(getIndexInVisibleArray(each), each);
852 hidden.remove();
853 update = true;
858 if (update) {
859 resetTabsCache();
860 if (mySelectedInfo != null && myHiddenInfos.containsKey(mySelectedInfo)) {
861 mySelectedInfo = getToSelectOnRemoveOf(mySelectedInfo);
863 updateAll(true, false);
867 private int getIndexInVisibleArray(TabInfo each) {
868 Integer index = myHiddenInfos.get(each);
869 if (index == null) {
870 index = Integer.valueOf(myVisibleInfos.size());
873 if (index > myVisibleInfos.size()) {
874 index = myVisibleInfos.size();
877 if (index.intValue() < 0) {
878 index = 0;
881 return index.intValue();
884 private void updateIcon(final TabInfo tabInfo) {
885 updateTab(new Runnable() {
886 public void run() {
887 myInfo2Label.get(tabInfo).setIcon(tabInfo.getIcon());
889 }, tabInfo);
892 private void updateColor(final TabInfo tabInfo) {
893 myInfo2Label.get(tabInfo).setInactiveStateImage(null);
895 updateTab(new Runnable() {
896 public void run() {
897 repaint();
899 }, tabInfo);
902 private void updateTab(Runnable update, TabInfo info) {
903 final TabLabel label = myInfo2Label.get(info);
904 final Dimension before = label.getPreferredSize();
905 update.run();
906 if (label.getRootPane() != null) {
907 final Dimension after = label.getPreferredSize();
908 if (after.equals(before)) {
909 label.repaint();
911 else {
912 revalidateAndRepaint(false);
917 void revalidateAndRepaint(final boolean layoutNow) {
919 if (myVisibleInfos.isEmpty()) {
920 setOpaque(false);
921 final Component nonOpaque = UIUtil.findUltimateParent(this);
922 if (nonOpaque != null && getParent() != null) {
923 final Rectangle toRepaint = SwingUtilities.convertRectangle(getParent(), getBounds(), nonOpaque);
924 nonOpaque.repaint(toRepaint.x, toRepaint.y, toRepaint.width, toRepaint.height);
927 else {
928 setOpaque(true);
931 if (layoutNow) {
932 validate();
934 else {
935 revalidate();
938 repaint();
942 private void updateAttraction(final TabInfo tabInfo, boolean start) {
943 if (start) {
944 myAttractions.add(tabInfo);
946 else {
947 myAttractions.remove(tabInfo);
948 tabInfo.setBlinkCount(0);
951 if (start && !myAnimator.isRunning()) {
952 myAnimator.resume();
954 else if (!start && myAttractions.isEmpty()) {
955 myAnimator.suspend();
956 repaintAttractions();
960 private void updateText(final TabInfo tabInfo) {
961 updateTab(new Runnable() {
962 public void run() {
963 final TabLabel label = myInfo2Label.get(tabInfo);
964 label.setText(tabInfo.getColoredText());
965 label.setToolTipText(tabInfo.getTooltipText());
967 }, tabInfo);
970 private void updateSideComponent(final TabInfo tabInfo) {
971 final Toolbar old = myInfo2Toolbar.get(tabInfo);
972 if (old != null) {
973 remove(old);
976 final Toolbar toolbar = createToolbarComponent(tabInfo);
977 myInfo2Toolbar.put(tabInfo, toolbar);
978 add(toolbar);
981 private void updateTabActions(final TabInfo info) {
982 myInfo2Label.get(info).setTabActions(info.getTabLabelActions());
985 @Nullable
986 public TabInfo getSelectedInfo() {
987 if (!myVisibleInfos.contains(mySelectedInfo)) {
988 mySelectedInfo = null;
990 return mySelectedInfo != null ? mySelectedInfo : !myVisibleInfos.isEmpty() ? myVisibleInfos.get(0) : null;
993 @Nullable
994 private TabInfo getToSelectOnRemoveOf(TabInfo info) {
995 if (!myVisibleInfos.contains(info)) return null;
996 if (mySelectedInfo != info) return null;
998 if (myVisibleInfos.size() == 1) return null;
1000 int index = myVisibleInfos.indexOf(info);
1002 TabInfo result = null;
1003 if (index > 0) {
1004 result = findEnabledBackward(index - 1);
1007 if (result == null) {
1008 result = findEnabledForward(index + 1);
1011 return result;
1014 private TabInfo findEnabledForward(int from) {
1015 int index = from;
1016 while (index < myVisibleInfos.size() && index >= 0) {
1017 final TabInfo each = myVisibleInfos.get(index);
1018 if (each.isEnabled()) return each;
1019 index++;
1022 return null;
1025 private TabInfo findEnabledBackward(int from) {
1026 int index = from;
1027 while (index >= 0 && from < myVisibleInfos.size()) {
1028 final TabInfo each = myVisibleInfos.get(index);
1029 if (each.isEnabled()) return each;
1030 index--;
1033 return null;
1036 protected Toolbar createToolbarComponent(final TabInfo tabInfo) {
1037 return new Toolbar(this, tabInfo);
1040 @NotNull
1041 public TabInfo getTabAt(final int tabIndex) {
1042 return getTabs().get(tabIndex);
1045 @NotNull
1046 public List<TabInfo> getTabs() {
1047 if (myAllTabs != null) return myAllTabs;
1049 ArrayList<TabInfo> result = new ArrayList<TabInfo>();
1050 result.addAll(myVisibleInfos);
1052 for (TabInfo each : myHiddenInfos.keySet()) {
1053 result.add(getIndexInVisibleArray(each), each);
1056 myAllTabs = result;
1058 return result;
1061 public TabInfo getTargetInfo() {
1062 return myPopupInfo != null ? myPopupInfo : getSelectedInfo();
1065 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
1068 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
1069 resetPopup();
1072 public void popupMenuCanceled(final PopupMenuEvent e) {
1073 resetPopup();
1076 private void resetPopup() {
1077 //todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled
1078 SwingUtilities.invokeLater(new Runnable() {
1079 public void run() {
1080 myPopupInfo = null;
1085 public void setPaintBlocked(boolean blocked, final boolean takeSnapshot) {
1086 if (blocked && !myPaintBlocked) {
1087 if (takeSnapshot) {
1088 myImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
1089 final Graphics2D g = myImage.createGraphics();
1090 super.paint(g);
1091 g.dispose();
1095 myPaintBlocked = blocked;
1097 if (!myPaintBlocked) {
1098 if (myImage != null) {
1099 myImage.flush();
1102 myImage = null;
1103 repaint();
1108 private void addToDeferredRemove(final Component c) {
1109 if (!myDeferredToRemove.containsKey(c)) {
1110 myDeferredToRemove.put(c, c);
1114 public boolean isToDrawBorderIfTabsHidden() {
1115 return myToDrawBorderIfTabsHidden;
1118 @NotNull
1119 public JBTabsPresentation setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden) {
1120 myToDrawBorderIfTabsHidden = toDrawBorderIfTabsHidden;
1121 return this;
1124 @NotNull
1125 public JBTabs getJBTabs() {
1126 return this;
1129 public static class Toolbar extends JPanel {
1130 private final JBTabsImpl myTabs;
1131 private final TabInfo myInfo;
1133 public Toolbar(JBTabsImpl tabs, TabInfo info) {
1134 myTabs = tabs;
1135 myInfo = info;
1137 setLayout(new BorderLayout());
1139 final ActionGroup group = info.getGroup();
1140 final JComponent side = info.getSideComponent();
1142 if (group != null && myTabs.myActionManager != null) {
1143 final String place = info.getPlace();
1144 ActionToolbar toolbar =
1145 myTabs.myActionManager.createActionToolbar(place != null ? place : ActionPlaces.UNKNOWN, group, myTabs.myHorizontalSide);
1146 toolbar.setTargetComponent(info.getActionsContextComponent());
1147 final JComponent actionToolbar = toolbar.getComponent();
1148 add(actionToolbar, BorderLayout.CENTER);
1151 if (side != null) {
1152 if (group != null) {
1153 add(side, BorderLayout.EAST);
1155 else {
1156 add(side, BorderLayout.CENTER);
1161 public boolean isEmpty() {
1162 return getComponentCount() == 0;
1167 public void doLayout() {
1168 try {
1169 myHeaderFitSize = computeHeaderFitSize();
1171 final Collection<TabLabel> labels = myInfo2Label.values();
1172 for (TabLabel each : labels) {
1173 each.setTabActionsAutoHide(myTabLabelActionsAutoHide);
1177 if (isSingleRow()) {
1178 myLastLayoutPass = mySingleRowLayout.layoutSingleRow();
1179 myTableLayout.myLastTableLayout = null;
1181 else {
1182 myLastLayoutPass = myTableLayout.layoutTable();
1183 mySingleRowLayout.myLastSingRowLayout = null;
1186 if (isStealthModeEffective() && !isHideTabs()) {
1187 final TabLabel label = getSelectedLabel();
1188 final Rectangle bounds = label.getBounds();
1189 final Insets insets = getLayoutInsets();
1190 layout(label, insets.left, bounds.y, getWidth() - insets.right - insets.left, bounds.height);
1194 moveDraggedTabLabel();
1196 myTabActionsAutoHideListener.processMouseOver();
1198 finally {
1199 myForcedRelayout = false;
1202 applyResetComponents();
1205 void moveDraggedTabLabel() {
1206 if (myDragHelper != null && myDragHelper.myDragRec != null) {
1207 final TabLabel selectedLabel = myInfo2Label.get(getSelectedInfo());
1208 if (selectedLabel != null) {
1209 final Rectangle bounds = selectedLabel.getBounds();
1210 if (isHorizontalTabs()) {
1211 selectedLabel.setBounds(myDragHelper.myDragRec.x, bounds.y, bounds.width, bounds.height);
1213 else {
1214 selectedLabel.setBounds(bounds.x, myDragHelper.myDragRec.y, bounds.width, bounds.height);
1220 private Dimension computeHeaderFitSize() {
1221 final Max max = computeMaxSize();
1223 if (myPosition == JBTabsPosition.top || myPosition == JBTabsPosition.bottom) {
1224 return new Dimension(getSize().width, myHorizontalSide ? Math.max(max.myLabel.height, max.myToolbar.height) : max.myLabel.height);
1226 else {
1227 return new Dimension(max.myLabel.width + (myHorizontalSide ? 0 : max.myToolbar.width), getSize().height);
1231 public Rectangle layoutComp(int componentX, int componentY, final JComponent comp, int deltaWidth, int deltaHeight) {
1232 final Insets insets = getLayoutInsets();
1234 final Insets border = isHideTabs() ? new Insets(0, 0, 0, 0) : myBorder.getEffectiveBorder();
1235 final boolean noTabsVisible = isStealthModeEffective() || isHideTabs();
1237 if (noTabsVisible) {
1238 border.top = getBorder(-1);
1239 border.bottom = getBorder(-1);
1240 border.left = getBorder(-1);
1241 border.right = getBorder(-1);
1244 final Insets inner = getInnerInsets();
1245 border.top += inner.top;
1246 border.bottom += inner.bottom;
1247 border.left += inner.left;
1248 border.right += inner.right;
1251 int x = insets.left + componentX + border.left;
1252 int y = insets.top + componentY + border.top;
1253 int width = getWidth() - insets.left - insets.right - componentX - border.left - border.right;
1254 int height = getHeight() - insets.top - insets.bottom - componentY - border.top - border.bottom;
1256 if (!noTabsVisible) {
1257 width += deltaWidth;
1258 height += deltaHeight;
1261 return layout(comp, x, y, width, height);
1265 public JBTabsPresentation setInnerInsets(final Insets innerInsets) {
1266 myInnerInsets = innerInsets;
1267 return this;
1270 public Insets getInnerInsets() {
1271 return myInnerInsets;
1274 public Insets getLayoutInsets() {
1275 Insets insets = getInsets();
1276 if (insets == null) {
1277 insets = new Insets(0, 0, 0, 0);
1279 return insets;
1282 private int fixInset(int inset, int addin) {
1283 return inset + addin;
1287 public int getToolbarInset() {
1288 return getArcSize() + 1;
1291 public void resetLayout(boolean resetLabels) {
1292 if (resetLabels) {
1293 mySingleRowLayout.myLeftGhost.reset();
1294 mySingleRowLayout.myRightGhost.reset();
1297 for (TabInfo each : myVisibleInfos) {
1298 reset(each, resetLabels);
1301 for (TabInfo each : myHiddenInfos.keySet()) {
1302 reset(each, resetLabels);
1305 for (Component eachDeferred : myDeferredToRemove.keySet()) {
1306 resetLayout((JComponent)eachDeferred);
1310 private void reset(final TabInfo each, final boolean resetLabels) {
1311 final JComponent c = each.getComponent();
1312 if (c != null) {
1313 resetLayout(c);
1316 resetLayout(myInfo2Toolbar.get(each));
1318 if (resetLabels) {
1319 resetLayout(myInfo2Label.get(each));
1324 private int getArcSize() {
1325 return 4;
1328 public int getGhostTabLength() {
1329 return 15;
1333 protected void paintComponent(final Graphics g) {
1334 super.paintComponent(g);
1336 if (myVisibleInfos.isEmpty()) return;
1338 Graphics2D g2d = (Graphics2D)g;
1340 final GraphicsConfig config = new GraphicsConfig(g2d);
1341 config.setAntialiasing(true);
1344 g2d.setColor(getBackground());
1345 final Rectangle clip = g2d.getClipBounds();
1346 g2d.fillRect(clip.x, clip.y, clip.width, clip.height);
1348 final TabInfo selected = getSelectedInfo();
1350 boolean leftGhostExists = isSingleRow();
1351 boolean rightGhostExists = isSingleRow();
1353 if (!isStealthModeEffective() && !isHideTabs()) {
1354 if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.lastGhostVisible) {
1355 paintLastGhost(g2d);
1359 paintNonSelectedTabs(g2d, leftGhostExists);
1361 if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.firstGhostVisible) {
1362 paintFirstGhost(g2d);
1367 config.setAntialiasing(false);
1369 if (isSideComponentVertical()) {
1370 Toolbar toolbarComp = myInfo2Toolbar.get(mySelectedInfo);
1371 if (toolbarComp != null && !toolbarComp.isEmpty()) {
1372 Rectangle toolBounds = toolbarComp.getBounds();
1373 g2d.setColor(CaptionPanel.CNT_ACTIVE_COLOR);
1374 g2d.drawLine((int)toolBounds.getMaxX(), toolBounds.y, (int)toolBounds.getMaxX(), (int)toolBounds.getMaxY() - 1);
1378 config.restore();
1381 private Color getActiveTabColor(final Color c) {
1382 final TabInfo info = getSelectedInfo();
1383 if (info == null) {
1384 return c;
1387 final Color tabColor = info.getTabColor();
1388 return tabColor == null ? c : tabColor;
1391 private void paintSelectionAndBorder(Graphics2D g2d) {
1392 if (getSelectedLabel() == null) return;
1394 final ShapeInfo shapeInfo = computeSelectedLabelShape();
1395 if (!isHideTabs()) {
1396 g2d.setColor(getBackground());
1397 g2d.fill(shapeInfo.fillPath.getShape());
1400 final int alpha;
1401 int paintTopY = shapeInfo.labelTopY;
1402 int paintBottomY = shapeInfo.labelBottomY;
1403 final boolean paintFocused = myPaintFocus && (myFocused || myActivePopup != null);
1404 Color bgPreFill = null;
1405 if (paintFocused) {
1406 final Color bgColor = getActiveTabColor(getActiveTabFillIn());
1407 if (bgColor == null) {
1408 shapeInfo.from = UIUtil.getFocusedFillColor();
1409 shapeInfo.to = UIUtil.getFocusedFillColor();
1411 else {
1412 bgPreFill = bgColor;
1413 alpha = 255;
1414 paintBottomY = shapeInfo.labelTopY + shapeInfo.labelPath.deltaY(getArcSize() - 2);
1415 shapeInfo.from = UIUtil.toAlpha(UIUtil.getFocusedFillColor(), alpha);
1416 shapeInfo.to = UIUtil.toAlpha(getActiveTabFillIn(), alpha);
1419 else {
1420 final Color bgColor = getActiveTabColor(getActiveTabFillIn());
1421 if (isPaintFocus()) {
1422 if (bgColor == null) {
1423 alpha = 150;
1424 shapeInfo.from = UIUtil.toAlpha(UIUtil.getPanelBackgound().brighter(), alpha);
1425 shapeInfo.to = UIUtil.toAlpha(UIUtil.getPanelBackgound(), alpha);
1427 else {
1428 alpha = 255;
1429 shapeInfo.from = UIUtil.toAlpha(bgColor, alpha);
1430 shapeInfo.to = UIUtil.toAlpha(bgColor, alpha);
1433 else {
1434 alpha = 255;
1435 final Color tabColor = getActiveTabColor(null);
1436 shapeInfo.from = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha);
1437 shapeInfo.to = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha);
1441 if (!isHideTabs()) {
1442 if (bgPreFill != null) {
1443 g2d.setColor(bgPreFill);
1444 g2d.fill(shapeInfo.fillPath.getShape());
1447 final Line2D.Float gradientLine =
1448 shapeInfo.fillPath.transformLine(shapeInfo.fillPath.getX(), paintTopY, shapeInfo.fillPath.getX(), paintBottomY);
1451 g2d.setPaint(new GradientPaint((float)gradientLine.getX1(), (float)gradientLine.getY1(),
1452 shapeInfo.fillPath.transformY1(shapeInfo.from, shapeInfo.to), (float)gradientLine.getX2(),
1453 (float)gradientLine.getY2(), shapeInfo.fillPath.transformY1(shapeInfo.to, shapeInfo.from)));
1454 g2d.fill(shapeInfo.fillPath.getShape());
1457 final Color tabColor = getActiveTabColor(null);
1458 Color borderColor = tabColor == null ? UIUtil.getBoundsColor(paintFocused) : tabColor.darker();
1459 g2d.setColor(borderColor);
1461 if (!isHideTabs()) {
1462 g2d.draw(shapeInfo.path.getShape());
1465 paintBorder(g2d, shapeInfo, borderColor);
1468 private ShapeInfo computeSelectedLabelShape() {
1469 final ShapeInfo shape = new ShapeInfo();
1471 shape.path = getEffectiveLayout().createShapeTransform(getSize());
1472 shape.insets = shape.path.transformInsets(getLayoutInsets());
1473 shape.labelPath = shape.path.createTransform(getSelectedLabel().getBounds());
1475 shape.labelBottomY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
1476 shape.labelTopY = shape.labelPath.getY();
1477 shape.labelLeftX = shape.labelPath.getX();
1478 shape.labelRightX = shape.labelPath.getX() + shape.labelPath.deltaX(shape.labelPath.getWidth());
1480 shape.path.moveTo(shape.insets.left, shape.labelBottomY);
1481 shape.path.lineTo(shape.labelLeftX, shape.labelBottomY);
1482 shape.path.lineTo(shape.labelLeftX, shape.labelTopY + shape.labelPath.deltaY(getArcSize()));
1483 shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getArcSize()), shape.labelTopY);
1485 int lastX = shape.path.getWidth() - shape.path.deltaX(shape.insets.right + 1);
1487 if (isStealthModeEffective()) {
1488 shape.path.lineTo(lastX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1489 shape.path.quadTo(lastX, shape.labelTopY, lastX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1490 shape.path.lineTo(lastX, shape.labelBottomY);
1492 else {
1493 shape.path.lineTo(shape.labelRightX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1494 shape.path.quadTo(shape.labelRightX, shape.labelTopY, shape.labelRightX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1495 if (myLastLayoutPass.hasCurveSpaceFor(getSelectedInfo())) {
1496 shape.path.lineTo(shape.labelRightX, shape.labelBottomY - shape.path.deltaY(getArcSize()));
1497 shape.path.quadTo(shape.labelRightX, shape.labelBottomY, shape.labelRightX + shape.path.deltaX(getArcSize()), shape.labelBottomY);
1499 else {
1500 shape.path.lineTo(shape.labelRightX, shape.labelBottomY);
1504 shape.path.lineTo(lastX, shape.labelBottomY);
1506 if (isStealthModeEffective()) {
1507 shape.path.closePath();
1510 shape.fillPath = shape.path.copy();
1511 if (!isHideTabs()) {
1512 shape.fillPath.lineTo(lastX, shape.labelBottomY + shape.fillPath.deltaY(1));
1513 shape.fillPath.lineTo(shape.labelLeftX, shape.labelBottomY + shape.fillPath.deltaY(1));
1514 shape.fillPath.closePath();
1516 return shape;
1519 private TabLabel getSelectedLabel() {
1520 return myInfo2Label.get(getSelectedInfo());
1523 static class ShapeInfo {
1524 ShapeTransform path;
1525 ShapeTransform fillPath;
1526 ShapeTransform labelPath;
1527 int labelBottomY;
1528 int labelTopY;
1529 int labelLeftX;
1530 int labelRightX;
1531 Insets insets;
1532 Color from;
1533 Color to;
1537 private void paintFirstGhost(Graphics2D g2d) {
1538 final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.firstGhost);
1540 int topX = path.getX() + path.deltaX(getCurveArc());
1541 int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1542 int bottomX = path.getMaxX() + path.deltaX(1);
1543 int bottomY = path.getMaxY() + path.deltaY(1);
1545 path.moveTo(topX, topY);
1547 final boolean isLeftFromSelection = mySingleRowLayout.myLastSingRowLayout.toLayout.indexOf(getSelectedInfo()) == 0;
1549 if (isLeftFromSelection) {
1550 path.lineTo(bottomX, topY);
1552 else {
1553 path.lineTo(bottomX - getArcSize(), topY);
1554 path.quadTo(bottomX, topY, bottomX, topY + path.deltaY(getArcSize()));
1557 path.lineTo(bottomX, bottomY);
1558 path.lineTo(topX, bottomY);
1560 path.quadTo(topX - path.deltaX(getCurveArc() * 2 - 1), bottomY - path.deltaY(Math.abs(bottomY - topY) / 4), topX,
1561 bottomY - path.deltaY(Math.abs(bottomY - topY) / 2));
1563 path.quadTo(topX + path.deltaX(getCurveArc() - 1), topY + path.deltaY(Math.abs(bottomY - topY) / 4), topX, topY);
1565 path.closePath();
1567 g2d.setColor(getBackground());
1568 g2d.fill(path.getShape());
1570 g2d.setColor(getBoundsColor());
1571 g2d.draw(path.getShape());
1573 g2d.setColor(getTopBlickColor());
1574 g2d.drawLine(topX + path.deltaX(1), topY + path.deltaY(1), bottomX - path.deltaX(getArcSize()), topY + path.deltaY(1));
1576 g2d.setColor(getRightBlockColor());
1577 g2d.drawLine(bottomX - path.deltaX(1), topY + path.deltaY(getArcSize()), bottomX - path.deltaX(1), bottomY - path.deltaY(1));
1580 private void paintLastGhost(Graphics2D g2d) {
1581 final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.lastGhost);
1583 int topX = path.getX() - path.deltaX(getArcSize());
1584 int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1585 int bottomX = path.getMaxX() - path.deltaX(getCurveArc());
1586 int bottomY = path.getMaxY() + path.deltaY(1);
1588 path.moveTo(topX, topY);
1589 path.lineTo(bottomX, topY);
1590 path.quadTo(bottomX - getCurveArc(), topY + (bottomY - topY) / 4, bottomX, topY + (bottomY - topY) / 2);
1591 path.quadTo(bottomX + getCurveArc(), bottomY - (bottomY - topY) / 4, bottomX, bottomY);
1592 path.lineTo(topX, bottomY);
1594 path.closePath();
1596 g2d.setColor(getBackground());
1597 g2d.fill(path.getShape());
1599 g2d.setColor(getBoundsColor());
1600 g2d.draw(path.getShape());
1602 g2d.setColor(getTopBlickColor());
1603 g2d.drawLine(topX, topY + path.deltaY(1), bottomX - path.deltaX(getCurveArc()), topY + path.deltaY(1));
1606 private int getCurveArc() {
1607 return 2;
1610 private Color getBoundsColor() {
1611 return Color.gray;
1614 private Color getRightBlockColor() {
1615 return Color.lightGray;
1618 private Color getTopBlickColor() {
1619 return Color.white;
1622 private void paintNonSelectedTabs(final Graphics2D g2d, final boolean leftGhostExists) {
1623 TabInfo selected = getSelectedInfo();
1624 if (myLastPaintedSelection == null || !myLastPaintedSelection.equals(selected)) {
1625 List<TabInfo> tabs = getTabs();
1626 for (TabInfo each : tabs) {
1627 myInfo2Label.get(each).setInactiveStateImage(null);
1631 for (int eachRow = 0; eachRow < myLastLayoutPass.getRowCount(); eachRow++) {
1632 for (int eachColumn = myLastLayoutPass.getColumnCount(eachRow) - 1; eachColumn >= 0; eachColumn--) {
1633 final TabInfo each = myLastLayoutPass.getTabAt(eachRow, eachColumn);
1634 if (getSelectedInfo() == each) {
1635 continue;
1637 paintNonSelected(g2d, each, leftGhostExists);
1641 myLastPaintedSelection = selected;
1644 private void paintNonSelected(final Graphics2D g2d, final TabInfo each, final boolean leftGhostExists) {
1645 final TabLabel label = myInfo2Label.get(each);
1646 if (label.getBounds().width == 0) return;
1648 int imageInsets = getArcSize() + 1;
1650 Rectangle bounds = label.getBounds();
1652 int x = bounds.x - imageInsets;
1653 int y = bounds.y;
1654 int width = bounds.width + imageInsets * 2 + 1;
1655 int height = bounds.height + getArcSize() + 1;
1657 if (isToBufferPainting()) {
1658 BufferedImage img = label.getInactiveStateImage(bounds);
1660 if (img == null) {
1661 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
1662 Graphics2D imgG2d = img.createGraphics();
1663 imgG2d.addRenderingHints(g2d.getRenderingHints());
1664 doPaintInactictive(imgG2d, leftGhostExists, label, new Rectangle(imageInsets, 0, label.getWidth(), label.getHeight()));
1665 imgG2d.dispose();
1668 g2d.drawImage(img, x, y, width, height, null);
1670 label.setInactiveStateImage(img);
1671 } else {
1672 doPaintInactictive(g2d, leftGhostExists, label, label.getBounds());
1673 label.setInactiveStateImage(null);
1677 private boolean isToBufferPainting() {
1678 return Registry.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint;
1681 private void doPaintInactictive(Graphics2D g2d, boolean leftGhostExists, TabLabel label, Rectangle effectiveBounds) {
1682 int tabIndex = myVisibleInfos.indexOf(label.getInfo());
1684 final int arc = getArcSize();
1685 Color topBlickColor = getTopBlickColor();
1686 Color rightBlockColor = getRightBlockColor();
1687 Color boundsColor = getBoundsColor();
1688 Color backgroundColor = getBackground();
1690 final Color tabColor = label.getInfo().getTabColor();
1691 if (tabColor != null) {
1692 backgroundColor = tabColor;
1693 boundsColor = tabColor.darker();
1694 topBlickColor = tabColor.brighter().brighter();
1695 rightBlockColor = tabColor;
1698 final TabInfo selected = getSelectedInfo();
1699 final int selectionTabVShift = getSelectionTabVShift();
1702 final TabInfo prev = myLastLayoutPass.getPreviousFor(myVisibleInfos.get(tabIndex));
1703 final TabInfo next = myLastLayoutPass.getNextFor(myVisibleInfos.get(tabIndex));
1706 boolean firstShowing = prev == null;
1707 if (!firstShowing && !leftGhostExists) {
1708 firstShowing = myInfo2Label.get(prev).getBounds().width == 0;
1711 boolean lastShowing = next == null;
1712 if (!lastShowing) {
1713 lastShowing = myInfo2Label.get(next).getBounds().width == 0;
1716 boolean leftFromSelection = selected != null && tabIndex == myVisibleInfos.indexOf(selected) - 1;
1718 Rectangle originalBounds = effectiveBounds;
1719 final ShapeTransform shape = getEffectiveLayout().createShapeTransform(originalBounds);
1721 int leftX = firstShowing ? shape.getX() : shape.getX() - shape.deltaX(arc + 1);
1722 int topY = shape.getY() + shape.deltaY(selectionTabVShift);
1723 int rigthX = !lastShowing && leftFromSelection ? shape.getMaxX() + shape.deltaX(arc + 1) : shape.getMaxX();
1724 int bottomY = shape.getMaxY() + shape.deltaY(1);
1726 shape.moveTo(leftX, bottomY);
1727 shape.lineTo(leftX, topY + shape.deltaY(arc));
1728 shape.quadTo(leftX, topY, leftX + shape.deltaX(arc), topY);
1729 shape.lineTo(rigthX - shape.deltaX(arc), topY);
1730 shape.quadTo(rigthX, topY, rigthX, topY + shape.deltaY(arc));
1731 shape.lineTo(rigthX, bottomY);
1733 if (!isSingleRow()) {
1734 final TablePassInfo info = myTableLayout.myLastTableLayout;
1735 if (!info.isInSelectionRow(label.getInfo())) {
1736 shape.lineTo(rigthX, bottomY + shape.deltaY(getArcSize()));
1737 shape.lineTo(leftX, bottomY + shape.deltaY(getArcSize()));
1738 shape.lineTo(leftX, bottomY);
1742 shape.closePath();
1744 g2d.setColor(backgroundColor);
1745 g2d.fill(shape.getShape());
1747 // TODO
1749 final Line2D.Float gradientLine =
1750 shape.transformLine(0, topY, 0, topY + shape.deltaY((int) (shape.getHeight() / 1.5 )));
1752 final GradientPaint gp =
1753 new GradientPaint(gradientLine.x1, gradientLine.y1,
1754 shape.transformY1(backgroundColor.brighter().brighter(), backgroundColor),
1755 gradientLine.x2, gradientLine.y2,
1756 shape.transformY1(backgroundColor, backgroundColor.brighter().brighter()));
1757 final Paint old = g2d.getPaint();
1758 g2d.setPaint(gp);
1759 g2d.fill(shape.getShape());
1760 g2d.setPaint(old);
1762 g2d.setColor(topBlickColor);
1763 g2d.draw(
1764 shape.transformLine(leftX + shape.deltaX(arc + 1), topY + shape.deltaY(1), rigthX - shape.deltaX(arc - 1), topY + shape.deltaY(1)));
1766 g2d.setColor(rightBlockColor);
1767 g2d.draw(shape.transformLine(rigthX - shape.deltaX(1), topY + shape.deltaY(arc - 1), rigthX - shape.deltaX(1), bottomY));
1769 g2d.setColor(boundsColor);
1770 g2d.draw(shape.getShape());
1773 public int getSelectionTabVShift() {
1774 return 2;
1777 private void paintBorder(Graphics2D g2d, ShapeInfo shape, final Color borderColor) {
1779 final ShapeTransform shaper = shape.path.copy().reset();
1781 final Insets paintBorder = shape.path.transformInsets(myBorder.getEffectiveBorder());
1783 int topY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
1785 int bottomY = topY + paintBorder.top - 2;
1786 int middleY = topY + (bottomY - topY) / 2;
1789 final int boundsX = shape.path.getX() + shape.path.deltaX(shape.insets.left);
1791 final int boundsY =
1792 isHideTabs() ? shape.path.getY() + shape.path.deltaY(shape.insets.top) : shape.labelPath.getMaxY() + shape.path.deltaY(1);
1794 final int boundsHeight = Math.abs(shape.path.getMaxY() - boundsY) - shape.insets.bottom - paintBorder.bottom;
1795 final int boundsWidth = Math.abs(shape.path.getMaxX() - (shape.insets.left + shape.insets.right));
1797 if (paintBorder.top > 0) {
1798 if (isHideTabs()) {
1799 if (isToDrawBorderIfTabsHidden()) {
1800 g2d.setColor(borderColor);
1801 g2d.fill(shaper.reset().doRect(boundsX, boundsY, boundsWidth, 1).getShape());
1804 else {
1805 Color tabFillColor = getActiveTabColor(null);
1806 if (tabFillColor == null) {
1807 tabFillColor = shape.path.transformY1(shape.to, shape.from);
1810 g2d.setColor(tabFillColor);
1811 g2d.fill(shaper.reset().doRect(boundsX, topY + shape.path.deltaY(1), boundsWidth, paintBorder.top - 1).getShape());
1813 g2d.setColor(borderColor);
1814 if (paintBorder.top == 2) {
1815 final Line2D.Float line = shape.path.transformLine(boundsX, topY, boundsX + shape.path.deltaX(boundsWidth - 1), topY);
1817 g2d.drawLine((int)line.x1, (int)line.y1, (int)line.x2, (int)line.y2);
1819 else if (paintBorder.top > 2) {
1820 //todo kirillk
1821 //start hack
1822 int deltaY = 0;
1823 if (myPosition == JBTabsPosition.bottom || myPosition == JBTabsPosition.right) {
1824 deltaY = 1;
1826 //end hack
1827 final int topLine = topY + shape.path.deltaY(paintBorder.top - 1);
1828 g2d.fill(shaper.reset().doRect(boundsX, topLine + deltaY, boundsWidth - 1, 1).getShape());
1833 g2d.setColor(borderColor);
1835 //bottom
1836 g2d.fill(shaper.reset().doRect(boundsX, Math.abs(shape.path.getMaxY() - shape.insets.bottom - paintBorder.bottom), boundsWidth,
1837 paintBorder.bottom).getShape());
1839 //left
1840 g2d.fill(shaper.reset().doRect(boundsX, boundsY, paintBorder.left, boundsHeight).getShape());
1842 //right
1843 g2d.fill(shaper.reset()
1844 .doRect(shape.path.getMaxX() - shape.insets.right - paintBorder.right, boundsY, paintBorder.right, boundsHeight).getShape());
1848 public boolean isStealthModeEffective() {
1849 return myStealthTabMode && getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition.top;
1853 private boolean isNavigationVisible() {
1854 if (myStealthTabMode && getTabCount() == 1) return false;
1855 return !myVisibleInfos.isEmpty();
1859 public void paint(final Graphics g) {
1860 Rectangle clip = g.getClipBounds();
1861 if (clip == null) {
1862 return;
1865 if (myPaintBlocked) {
1866 if (myImage != null) {
1867 g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null);
1869 return;
1872 super.paint(g);
1875 protected void paintChildren(final Graphics g) {
1876 super.paintChildren(g);
1878 final GraphicsConfig config = new GraphicsConfig(g);
1879 config.setAntialiasing(true);
1880 paintSelectionAndBorder((Graphics2D)g);
1881 config.restore();
1883 final TabLabel selected = getSelectedLabel();
1884 if (selected != null) {
1885 selected.paintImage(g);
1888 mySingleRowLayout.myMoreIcon.paintIcon(this, g);
1891 private Max computeMaxSize() {
1892 Max max = new Max();
1893 for (TabInfo eachInfo : myVisibleInfos) {
1894 final TabLabel label = myInfo2Label.get(eachInfo);
1895 max.myLabel.height = Math.max(max.myLabel.height, label.getPreferredSize().height);
1896 max.myLabel.width = Math.max(max.myLabel.width, label.getPreferredSize().width);
1897 final Toolbar toolbar = myInfo2Toolbar.get(eachInfo);
1898 if (myLayout.isSideComponentOnTabs() && toolbar != null && !toolbar.isEmpty()) {
1899 max.myToolbar.height = Math.max(max.myToolbar.height, toolbar.getPreferredSize().height);
1900 max.myToolbar.width = Math.max(max.myToolbar.width, toolbar.getPreferredSize().width);
1904 max.myToolbar.height++;
1906 return max;
1909 public Dimension getMinimumSize() {
1910 return computeSize(new Transform<JComponent, Dimension>() {
1911 public Dimension transform(JComponent component) {
1912 return component.getMinimumSize();
1914 }, 1);
1917 public Dimension getPreferredSize() {
1918 return computeSize(new Transform<JComponent, Dimension>() {
1919 public Dimension transform(JComponent component) {
1920 return component.getPreferredSize();
1922 }, 3);
1925 private Dimension computeSize(Transform<JComponent, Dimension> transform, int tabCount) {
1926 Dimension size = new Dimension();
1927 for (TabInfo each : myVisibleInfos) {
1928 final JComponent c = each.getComponent();
1929 if (c != null) {
1930 final Dimension eachSize = transform.transform(c);
1931 size.width = Math.max(eachSize.width, size.width);
1932 size.height = Math.max(eachSize.height, size.height);
1936 addHeaderSize(size, tabCount);
1937 return size;
1940 private void addHeaderSize(Dimension size, final int tabsCount) {
1941 Dimension header = computeHeaderPreferredSize(tabsCount);
1943 size.height += header.height;
1944 size.width += header.width;
1946 final Insets insets = getLayoutInsets();
1947 size.width += insets.left + insets.right + 1;
1948 size.height += insets.top + insets.bottom + 1;
1951 private Dimension computeHeaderPreferredSize(int tabsCount) {
1952 final Iterator<TabInfo> infos = myInfo2Label.keySet().iterator();
1953 Dimension size = new Dimension();
1954 int currentTab = 0;
1956 final boolean horizontal = getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
1958 while (infos.hasNext()) {
1959 final boolean canGrow = currentTab < tabsCount;
1961 TabInfo eachInfo = infos.next();
1962 final TabLabel eachLabel = myInfo2Label.get(eachInfo);
1963 final Dimension eachPrefSize = eachLabel.getPreferredSize();
1964 if (horizontal) {
1965 if (canGrow) {
1966 size.width += eachPrefSize.width;
1968 size.height = Math.max(size.height, eachPrefSize.height);
1970 else {
1971 size.width = Math.max(size.width, eachPrefSize.width);
1972 if (canGrow) {
1973 size.height += eachPrefSize.height;
1977 currentTab++;
1980 if (isSingleRow() && isGhostsAlwaysVisible()) {
1981 if (horizontal) {
1982 size.width += getGhostTabLength() * 2;
1984 else {
1985 size.height += getGhostTabLength() * 2;
1989 if (horizontal) {
1990 size.height += myBorder.getTabBorderSize();
1992 else {
1993 size.width += myBorder.getTabBorderSize();
1996 return size;
1999 public int getTabCount() {
2000 return getTabs().size();
2003 @NotNull
2004 public JBTabsPresentation getPresentation() {
2005 return this;
2008 public ActionCallback removeTab(final JComponent component) {
2009 return removeTab(findInfo(component));
2012 public ActionCallback removeTab(final TabInfo info) {
2013 return removeTab(info, null);
2016 public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer) {
2017 return removeTab(info, forcedSelectionTranfer, true);
2020 public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer, boolean transferFocus) {
2021 if (info == null || !getTabs().contains(info)) return new ActionCallback.Done();
2023 final ActionCallback result = new ActionCallback();
2025 TabInfo toSelect;
2026 if (forcedSelectionTranfer == null) {
2027 toSelect = getToSelectOnRemoveOf(info);
2029 else {
2030 assert myVisibleInfos.contains(forcedSelectionTranfer) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer;
2031 toSelect = forcedSelectionTranfer;
2035 if (toSelect != null) {
2036 processRemove(info, false);
2037 _setSelected(toSelect, transferFocus).doWhenProcessed(new Runnable() {
2038 public void run() {
2039 removeDeferred().notifyWhenDone(result);
2043 else {
2044 processRemove(info, true);
2045 removeDeferred().notifyWhenDone(result);
2048 if (myVisibleInfos.isEmpty()) {
2049 removeDeferredNow();
2052 revalidateAndRepaint(true);
2054 return result;
2057 private void processRemove(final TabInfo info, boolean forcedNow) {
2058 remove(myInfo2Label.get(info));
2059 remove(myInfo2Toolbar.get(info));
2061 JComponent tabComponent = info.getComponent();
2063 if (!isToDeferRemoveForLater(tabComponent) || forcedNow) {
2064 remove(tabComponent);
2066 else {
2067 queueForRemove(tabComponent);
2070 myVisibleInfos.remove(info);
2071 myHiddenInfos.remove(info);
2072 myInfo2Label.remove(info);
2073 myInfo2Toolbar.remove(info);
2074 resetTabsCache();
2076 updateAll(false, false);
2079 public TabInfo findInfo(Component component) {
2080 for (TabInfo each : getTabs()) {
2081 if (each.getComponent() == component) return each;
2084 return null;
2087 public TabInfo findInfo(String text) {
2088 if (text == null) return null;
2090 for (TabInfo each : getTabs()) {
2091 if (text.equals(each.getText())) return each;
2094 return null;
2097 public TabInfo findInfo(MouseEvent event) {
2098 return findInfo(event, false);
2101 private TabInfo findInfo(final MouseEvent event, final boolean labelsOnly) {
2102 final Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this);
2103 return _findInfo(point, labelsOnly);
2106 public TabInfo findInfo(final Object object) {
2107 for (int i = 0; i < getTabCount(); i++) {
2108 final TabInfo each = getTabAt(i);
2109 final Object eachObject = each.getObject();
2110 if (eachObject != null && eachObject.equals(object)) return each;
2112 return null;
2115 public TabInfo findTabLabelBy(final Point point) {
2116 return _findInfo(point, true);
2119 private TabInfo _findInfo(final Point point, boolean labelsOnly) {
2120 Component component = findComponentAt(point);
2121 if (component == null) return null;
2122 while (component != this || component != null) {
2123 if (component instanceof TabLabel) {
2124 return ((TabLabel)component).getInfo();
2126 else if (!labelsOnly) {
2127 final TabInfo info = findInfo(component);
2128 if (info != null) return info;
2130 if (component == null) break;
2131 component = component.getParent();
2134 return null;
2137 public void removeAllTabs() {
2138 for (TabInfo each : getTabs()) {
2139 removeTab(each);
2144 private static class Max {
2145 Dimension myLabel = new Dimension();
2146 Dimension myToolbar = new Dimension();
2149 private void updateContainer(boolean forced, final boolean layoutNow) {
2150 final TabLabel selectedLabel = getSelectedLabel();
2152 for (TabInfo each : myVisibleInfos) {
2153 final JComponent eachComponent = each.getComponent();
2154 if (getSelectedInfo() == each && getSelectedInfo() != null) {
2155 unqueueFromRemove(eachComponent);
2157 final Container parent = eachComponent.getParent();
2158 if (parent != null && parent != this) {
2159 parent.remove(eachComponent);
2162 if (eachComponent.getParent() == null) {
2163 add(eachComponent);
2166 else {
2167 if (eachComponent.getParent() == null) continue;
2168 if (isToDeferRemoveForLater(eachComponent)) {
2169 queueForRemove(eachComponent);
2171 else {
2172 remove(eachComponent);
2177 relayout(forced, layoutNow);
2180 protected void addImpl(final Component comp, final Object constraints, final int index) {
2181 unqueueFromRemove(comp);
2183 if (comp instanceof TabLabel) {
2184 ((TabLabel)comp).apply(myUiDecorator.getDecoration());
2187 super.addImpl(comp, constraints, index);
2191 private boolean isToDeferRemoveForLater(JComponent c) {
2192 return c.getRootPane() != null;
2195 void relayout(boolean forced, final boolean layoutNow) {
2196 if (!myForcedRelayout) {
2197 myForcedRelayout = forced;
2199 revalidateAndRepaint(layoutNow);
2202 ActionManager getActionManager() {
2203 return myActionManager;
2206 public TabsBorder getTabsBorder() {
2207 return myBorder;
2210 @NotNull
2211 public JBTabs addTabMouseMotionListener(@NotNull MouseMotionListener listener) {
2212 removeListeners();
2213 myTabMouseListeners.add(listener);
2214 addListeners();
2215 return this;
2218 @NotNull
2219 public JBTabs addTabMouseListener(@NotNull MouseListener listener) {
2220 removeListeners();
2221 myTabMouseListeners.add(listener);
2222 addListeners();
2223 return this;
2226 @NotNull
2227 public JComponent getComponent() {
2228 return this;
2231 @NotNull
2232 public JBTabs removeTabMouseListener(@NotNull MouseListener listener) {
2233 removeListeners();
2234 myTabMouseListeners.remove(listener);
2235 addListeners();
2236 return this;
2239 private void addListeners() {
2240 for (TabInfo eachInfo : myVisibleInfos) {
2241 final TabLabel label = myInfo2Label.get(eachInfo);
2242 for (EventListener eachListener : myTabMouseListeners) {
2243 if (eachListener instanceof MouseListener) {
2244 label.addMouseListener((MouseListener)eachListener);
2246 else if (eachListener instanceof MouseMotionListener) {
2247 label.addMouseMotionListener((MouseMotionListener)eachListener);
2249 else {
2250 assert false;
2256 private void removeListeners() {
2257 for (TabInfo eachInfo : myVisibleInfos) {
2258 final TabLabel label = myInfo2Label.get(eachInfo);
2259 for (EventListener eachListener : myTabMouseListeners) {
2260 if (eachListener instanceof MouseListener) {
2261 label.removeMouseListener((MouseListener)eachListener);
2263 else if (eachListener instanceof MouseMotionListener) {
2264 label.removeMouseMotionListener((MouseMotionListener)eachListener);
2266 else {
2267 assert false;
2273 private void updateListeners() {
2274 removeListeners();
2275 addListeners();
2278 public JBTabs addListener(@NotNull TabsListener listener) {
2279 myTabListeners.add(listener);
2280 return this;
2283 public JBTabs removeListener(@NotNull final TabsListener listener) {
2284 myTabListeners.remove(listener);
2285 return this;
2288 protected void onPopup(final TabInfo popupInfo) {
2291 public void setFocused(final boolean focused) {
2292 if (myFocused == focused) return;
2294 myFocused = focused;
2296 if (myPaintFocus) {
2297 repaint();
2301 public int getIndexOf(@Nullable final TabInfo tabInfo) {
2302 return myVisibleInfos.indexOf(tabInfo);
2305 public boolean isHideTabs() {
2306 return myHideTabs;
2309 public void setHideTabs(final boolean hideTabs) {
2310 if (isHideTabs() == hideTabs) return;
2312 myHideTabs = hideTabs;
2314 relayout(true, false);
2317 public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) {
2318 return myBorder.setPaintBorder(top, left, right, bottom);
2321 public JBTabsPresentation setTabSidePaintBorder(int size) {
2322 return myBorder.setTabSidePaintBorder(size);
2325 static int getBorder(int size) {
2326 return size == -1 ? 1 : size;
2329 public boolean isPaintFocus() {
2330 return myPaintFocus;
2333 @NotNull
2334 public JBTabsPresentation setAdjustBorders(final boolean adjust) {
2335 myAdjustBorders = adjust;
2336 return this;
2339 @NotNull
2340 public JBTabsPresentation setActiveTabFillIn(@Nullable final Color color) {
2341 if (!isChanged(myActiveTabFillIn, color)) return this;
2343 myActiveTabFillIn = color;
2344 revalidateAndRepaint(false);
2345 return this;
2348 private boolean isChanged(Object oldObject, Object newObject) {
2349 if (oldObject == null && newObject == null) return false;
2350 return oldObject != null && !oldObject.equals(newObject) || newObject != null && !newObject.equals(oldObject);
2353 @NotNull
2354 public JBTabsPresentation setTabLabelActionsAutoHide(final boolean autoHide) {
2355 if (myTabLabelActionsAutoHide != autoHide) {
2356 myTabLabelActionsAutoHide = autoHide;
2357 revalidateAndRepaint(false);
2359 return this;
2362 @Nullable
2363 public Color getActiveTabFillIn() {
2364 return myActiveTabFillIn;
2367 public JBTabsPresentation setFocusCycle(final boolean root) {
2368 setFocusCycleRoot(root);
2369 return this;
2373 public JBTabsPresentation setPaintFocus(final boolean paintFocus) {
2374 myPaintFocus = paintFocus;
2375 return this;
2378 private abstract static class BaseNavigationAction extends AnAction {
2380 private final ShadowAction myShadow;
2381 private final ActionManager myActionManager;
2382 private final JBTabsImpl myTabs;
2384 protected BaseNavigationAction(final String copyFromID, JBTabsImpl tabs, ActionManager mgr) {
2385 myActionManager = mgr;
2386 myTabs = tabs;
2387 myShadow = new ShadowAction(this, myActionManager.getAction(copyFromID), tabs);
2388 Disposer.register(tabs, myShadow);
2389 setEnabledInModalContext(true);
2392 public final void update(final AnActionEvent e) {
2393 JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2394 e.getPresentation().setVisible(tabs != null);
2395 if (tabs == null) return;
2397 final int selectedIndex = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2398 final boolean enabled = tabs == myTabs && myTabs.isNavigationVisible() && selectedIndex >= 0 && myTabs.myNavigationActionsEnabled;
2399 e.getPresentation().setEnabled(enabled);
2400 if (enabled) {
2401 _update(e, tabs, selectedIndex);
2405 public void reconnect(String actionId) {
2406 myShadow.reconnect(myActionManager.getAction(actionId));
2409 protected abstract void _update(AnActionEvent e, final JBTabsImpl tabs, int selectedIndex);
2411 public final void actionPerformed(final AnActionEvent e) {
2412 JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2413 if (tabs == null) return;
2415 final int index = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2416 if (index == -1) return;
2417 _actionPerformed(e, tabs, index);
2420 protected abstract void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex);
2423 private static class SelectNextAction extends BaseNavigationAction {
2425 private SelectNextAction(JBTabsImpl tabs, ActionManager mgr) {
2426 super(IdeActions.ACTION_NEXT_TAB, tabs, mgr);
2429 protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2430 e.getPresentation().setEnabled(tabs.findEnabledForward(selectedIndex + 1) != null);
2433 protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2434 tabs.select(tabs.findEnabledForward(selectedIndex + 1), true);
2438 private static class SelectPreviousAction extends BaseNavigationAction {
2439 private SelectPreviousAction(JBTabsImpl tabs, ActionManager mgr) {
2440 super(IdeActions.ACTION_PREVIOUS_TAB, tabs, mgr);
2443 protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2444 e.getPresentation().setEnabled(tabs.findEnabledBackward(selectedIndex - 1) != null);
2447 protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2448 tabs.select(tabs.findEnabledBackward(selectedIndex - 1), true);
2452 private void disposePopupListener() {
2453 if (myActivePopup != null) {
2454 myActivePopup.removePopupMenuListener(myPopupListener);
2455 myActivePopup = null;
2459 public JBTabsPresentation setStealthTabMode(final boolean stealthTabMode) {
2460 myStealthTabMode = stealthTabMode;
2462 relayout(true, false);
2464 return this;
2467 public boolean isStealthTabMode() {
2468 return myStealthTabMode;
2471 public JBTabsPresentation setSideComponentVertical(final boolean vertical) {
2472 myHorizontalSide = !vertical;
2474 for (TabInfo each : myVisibleInfos) {
2475 each.getChangeSupport().firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2");
2479 relayout(true, false);
2481 return this;
2484 public JBTabsPresentation setSingleRow(boolean singleRow) {
2485 myLayout = singleRow ? mySingleRowLayout : myTableLayout;
2487 relayout(true, false);
2489 return this;
2492 public JBTabsPresentation setGhostsAlwaysVisible(final boolean visible) {
2493 myGhostsAlwaysVisible = visible;
2495 relayout(true, false);
2497 return this;
2500 public boolean isGhostsAlwaysVisible() {
2501 return myGhostsAlwaysVisible;
2504 public boolean isSingleRow() {
2505 return getEffectiveLayout() == mySingleRowLayout;
2508 public boolean isSideComponentVertical() {
2509 return !myHorizontalSide;
2512 private TabLayout getEffectiveLayout() {
2513 if (myLayout == myTableLayout && getTabsPosition() == JBTabsPosition.top) return myTableLayout;
2514 return mySingleRowLayout;
2517 public JBTabsPresentation setUiDecorator(UiDecorator decorator) {
2518 myUiDecorator = decorator == null ? ourDefaultDecorator : decorator;
2519 applyDecoration();
2520 return this;
2523 protected void setUI(final ComponentUI newUI) {
2524 super.setUI(newUI);
2525 applyDecoration();
2528 public void updateUI() {
2529 super.updateUI();
2530 SwingUtilities.invokeLater(new Runnable() {
2531 public void run() {
2532 applyDecoration();
2534 revalidateAndRepaint(false);
2539 private void applyDecoration() {
2540 if (myUiDecorator != null) {
2541 UiDecorator.UiDecoration uiDecoration = myUiDecorator.getDecoration();
2542 for (TabLabel each : myInfo2Label.values()) {
2543 each.apply(uiDecoration);
2548 for (TabInfo each : getTabs()) {
2549 adjust(each);
2552 relayout(true, false);
2555 private void adjust(final TabInfo each) {
2556 if (myAdjustBorders) {
2557 UIUtil.removeScrollBorder(each.getComponent());
2561 public void sortTabs(Comparator<TabInfo> comparator) {
2562 Collections.sort(myVisibleInfos, comparator);
2564 relayout(true, false);
2567 public boolean isRequestFocusOnLastFocusedComponent() {
2568 return myRequestFocusOnLastFocusedComponent;
2571 public JBTabsPresentation setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent) {
2572 myRequestFocusOnLastFocusedComponent = requestFocusOnLastFocusedComponent;
2573 return this;
2577 @Nullable
2578 public Object getData(@NonNls final String dataId) {
2579 if (myDataProvider != null) {
2580 final Object value = myDataProvider.getData(dataId);
2581 if (value != null) return value;
2584 return NAVIGATION_ACTIONS_KEY.is(dataId) ? this : null;
2588 public DataProvider getDataProvider() {
2589 return myDataProvider;
2592 public JBTabsImpl setDataProvider(@NotNull final DataProvider dataProvider) {
2593 myDataProvider = dataProvider;
2594 return this;
2598 public boolean isSelectionClick(final MouseEvent e, boolean canBeQuick) {
2599 if (e.getClickCount() == 1 || canBeQuick) {
2600 if (!e.isPopupTrigger()) {
2601 return e.getButton() == MouseEvent.BUTTON1 && !e.isControlDown() && !e.isAltDown() && !e.isMetaDown();
2605 return false;
2609 private static class DefautDecorator implements UiDecorator {
2610 @NotNull
2611 public UiDecoration getDecoration() {
2612 return new UiDecoration(null, new Insets(1, 4, 1, 5));
2616 public static Rectangle layout(JComponent c, Rectangle bounds) {
2617 final Rectangle now = c.getBounds();
2618 if (!bounds.equals(now)) {
2619 c.setBounds(bounds);
2621 c.putClientProperty(LAYOUT_DONE, Boolean.TRUE);
2623 return bounds;
2626 public static Rectangle layout(JComponent c, int x, int y, int width, int height) {
2627 return layout(c, new Rectangle(x, y, width, height));
2630 public static void resetLayout(JComponent c) {
2631 if (c == null) return;
2632 c.putClientProperty(LAYOUT_DONE, null);
2635 private void applyResetComponents() {
2636 for (int i = 0; i < getComponentCount(); i++) {
2637 final Component each = getComponent(i);
2638 if (each instanceof JComponent) {
2639 final JComponent jc = (JComponent)each;
2640 final Object done = jc.getClientProperty(LAYOUT_DONE);
2641 if (!Boolean.TRUE.equals(done)) {
2642 layout(jc, new Rectangle(0, 0, 0, 0));
2649 @NotNull
2650 public JBTabsPresentation setTabLabelActionsMouseDeadzone(final TimedDeadzone.Length length) {
2651 myTabActionsMouseDeadzone = length;
2652 final List<TabInfo> all = getTabs();
2653 for (TabInfo each : all) {
2654 final TabLabel eachLabel = myInfo2Label.get(each);
2655 eachLabel.updateTabActions();
2657 return this;
2660 @NotNull
2661 public JBTabsPresentation setTabsPosition(final JBTabsPosition position) {
2662 myPosition = position;
2663 relayout(true, false);
2664 return this;
2667 public JBTabsPosition getTabsPosition() {
2668 return myPosition;
2671 public TimedDeadzone.Length getTabActionsMouseDeadzone() {
2672 return myTabActionsMouseDeadzone;
2675 public JBTabsPresentation setTabDraggingEnabled(boolean enabled) {
2676 myTabDraggingEnabled = enabled;
2677 return this;
2680 public boolean isTabDraggingEnabled() {
2681 return myTabDraggingEnabled && isSingleRow();
2684 void reallocate(TabInfo source, TabInfo target, boolean before) {
2685 if (source == target || source == null || target == null) return;
2687 final int targetIndex = myVisibleInfos.indexOf(target);
2688 final int sourceIndex = myVisibleInfos.indexOf(source);
2690 boolean needsValidation = false;
2692 myVisibleInfos.remove(source);
2693 myVisibleInfos.add(targetIndex, source);
2694 needsValidation = true;
2696 //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) {
2699 if (needsValidation) {
2700 invalidate();
2701 relayout(true, true);
2705 boolean isHorizontalTabs() {
2706 return getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
2709 public void putInfo(Map<String, String> info) {
2710 final TabInfo selected = getSelectedInfo();
2711 if (selected != null) {
2712 selected.putInfo(info);
2716 public boolean isUseBufferedPaint() {
2717 return myUseBufferedPaint;
2720 public void setUseBufferedPaint(boolean useBufferedPaint) {
2721 myUseBufferedPaint = useBufferedPaint;
2722 revalidate();
2723 repaint();