tabs: fixing invalidation on tab property changes
[fedora-idea.git] / platform / platform-api / src / com / intellij / ui / tabs / impl / JBTabsImpl.java
blob85054ea3447e5a72344ad8db3e222e246d9ce181
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.FocusCommand;
27 import com.intellij.openapi.wm.IdeFocusManager;
28 import com.intellij.openapi.wm.IdeGlassPane;
29 import com.intellij.openapi.wm.IdeGlassPaneUtil;
30 import com.intellij.openapi.wm.impl.content.GraphicsConfig;
31 import com.intellij.ui.CaptionPanel;
32 import com.intellij.ui.tabs.*;
33 import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout;
34 import com.intellij.ui.tabs.impl.singleRow.SingleRowPassInfo;
35 import com.intellij.ui.tabs.impl.table.TableLayout;
36 import com.intellij.ui.tabs.impl.table.TablePassInfo;
37 import com.intellij.util.ui.Animator;
38 import com.intellij.util.ui.TimedDeadzone;
39 import com.intellij.util.ui.UIUtil;
40 import com.intellij.util.ui.update.LazyUiDisposable;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
45 import javax.swing.*;
46 import javax.swing.event.PopupMenuEvent;
47 import javax.swing.event.PopupMenuListener;
48 import javax.swing.plaf.ComponentUI;
49 import java.awt.*;
50 import java.awt.event.*;
51 import java.awt.geom.Line2D;
52 import java.awt.image.BufferedImage;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.util.*;
56 import java.util.List;
58 public class JBTabsImpl extends JComponent
59 implements JBTabs, PropertyChangeListener, TimerListener, DataProvider, PopupMenuListener, Disposable, JBTabsPresentation, TestableUi {
61 static DataKey<JBTabsImpl> NAVIGATION_ACTIONS_KEY = DataKey.create("JBTabs");
63 ActionManager myActionManager;
64 public final List<TabInfo> myVisibleInfos = new ArrayList<TabInfo>();
65 private final Map<TabInfo, Integer> myHiddenInfos = new HashMap<TabInfo, Integer>();
67 private TabInfo mySelectedInfo;
68 public final Map<TabInfo, TabLabel> myInfo2Label = new HashMap<TabInfo, TabLabel>();
69 public final Map<TabInfo, Toolbar> myInfo2Toolbar = new HashMap<TabInfo, Toolbar>();
70 public Dimension myHeaderFitSize;
72 private Insets myInnerInsets = new Insets(0, 0, 0, 0);
74 private final List<EventListener> myTabMouseListeners = new ArrayList<EventListener>();
75 private final List<TabsListener> myTabListeners = new ArrayList<TabsListener>();
76 public boolean myFocused;
78 private Getter<ActionGroup> myPopupGroup;
79 private String myPopupPlace;
81 TabInfo myPopupInfo;
82 DefaultActionGroup myNavigationActions;
84 PopupMenuListener myPopupListener;
85 JPopupMenu myActivePopup;
87 public boolean myHorizontalSide = true;
89 private boolean myStealthTabMode = false;
91 private DataProvider myDataProvider;
93 private final WeakHashMap<Component, Component> myDeferredToRemove = new WeakHashMap<Component, Component>();
95 private final SingleRowLayout mySingleRowLayout = new SingleRowLayout(this);
96 private final TableLayout myTableLayout = new TableLayout(this);
99 private TabLayout myLayout = mySingleRowLayout;
100 LayoutPassInfo myLastLayoutPass;
101 private TabInfo myLastPaintedSelection;
103 public boolean myForcedRelayout;
105 private UiDecorator myUiDecorator;
106 static final UiDecorator ourDefaultDecorator = new DefautDecorator();
108 private boolean myPaintFocus;
110 private boolean myHideTabs = false;
111 @Nullable private Project myProject;
113 private boolean myRequestFocusOnLastFocusedComponent = false;
114 private boolean myListenerAdded;
115 final Set<TabInfo> myAttractions = new HashSet<TabInfo>();
116 private Animator myAnimator;
117 private List<TabInfo> myAllTabs;
118 private boolean myPaintBlocked;
119 private BufferedImage myImage;
120 private IdeFocusManager myFocusManager;
121 private boolean myAdjustBorders = true;
123 boolean myAddNavigationGroup = true;
125 private boolean myGhostsAlwaysVisible = false;
126 private boolean myDisposed;
127 private boolean myToDrawBorderIfTabsHidden = true;
128 private Color myActiveTabFillIn;
130 private boolean myTabLabelActionsAutoHide;
132 private final TabActionsAutoHideListener myTabActionsAutoHideListener = new TabActionsAutoHideListener();
133 private IdeGlassPane myGlassPane;
134 @NonNls private static final String LAYOUT_DONE = "Layout.done";
136 private TimedDeadzone.Length myTabActionsMouseDeadzone = TimedDeadzone.DEFAULT;
138 private long myRemoveDefferredRequest;
139 private boolean myTestMode;
141 private JBTabsPosition myPosition = JBTabsPosition.top;
143 private final TabsBorder myBorder = new TabsBorder(this);
144 private BaseNavigationAction myNextAction;
145 private BaseNavigationAction myPrevAction;
147 private boolean myWasEverShown;
149 private boolean myTabDraggingEnabled;
150 private DragHelper myDragHelper;
151 private boolean myNavigationActionsEnabled = true;
152 private boolean myUseBufferedPaint = true;
154 public JBTabsImpl(@NotNull Project project) {
155 this(project, project);
158 public JBTabsImpl(@NotNull Project project, @NotNull Disposable parent) {
159 this(project, ActionManager.getInstance(), IdeFocusManager.getInstance(project), parent);
162 public JBTabsImpl(@Nullable Project project, IdeFocusManager focusManager, @NotNull Disposable parent) {
163 this(project, ActionManager.getInstance(), focusManager, parent);
166 public JBTabsImpl(@Nullable Project project, ActionManager actionManager, IdeFocusManager focusManager, @NotNull Disposable parent) {
167 myProject = project;
168 myActionManager = actionManager;
169 myFocusManager = focusManager != null ? focusManager : IdeFocusManager.getGlobalInstance();
171 setOpaque(true);
172 setPaintBorder(-1, -1, -1, -1);
174 Disposer.register(parent, this);
176 myNavigationActions = new DefaultActionGroup();
178 if (myActionManager != null) {
179 myNextAction = new SelectNextAction(this, myActionManager);
180 myPrevAction = new SelectPreviousAction(this, myActionManager);
182 myNavigationActions.add(myNextAction);
183 myNavigationActions.add(myPrevAction);
186 setUiDecorator(null);
188 myPopupListener = new PopupMenuListener() {
189 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
192 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
193 disposePopupListener();
196 public void popupMenuCanceled(final PopupMenuEvent e) {
197 disposePopupListener();
201 addMouseListener(new MouseAdapter() {
202 public void mousePressed(final MouseEvent e) {
203 if (mySingleRowLayout.myLastSingRowLayout != null &&
204 mySingleRowLayout.myLastSingRowLayout.moreRect != null &&
205 mySingleRowLayout.myLastSingRowLayout.moreRect.contains(e.getPoint())) {
206 showMorePopup(e);
211 myAnimator = new Animator("JBTabs Attractions", 2, 500, true, 0, -1) {
212 public void paintNow(final float frame, final float totalFrames, final float cycle) {
213 repaintAttractions();
216 myAnimator.setTakInitialDelay(false);
218 setFocusCycleRoot(true);
219 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
220 public Component getDefaultComponent(final Container aContainer) {
221 return getToFocus();
225 add(mySingleRowLayout.myLeftGhost);
226 add(mySingleRowLayout.myRightGhost);
229 new LazyUiDisposable<JBTabsImpl>(parent, this, this, project) {
230 protected void initialize(@NotNull Disposable parent, @NotNull JBTabsImpl child, @Nullable Project project) {
231 myProject = project;
233 Disposer.register(child, myAnimator);
234 Disposer.register(child, new Disposable() {
235 public void dispose() {
236 removeTimerUpdate();
240 if (!myTestMode) {
241 final IdeGlassPane gp = IdeGlassPaneUtil.find(child);
242 if (gp != null) {
243 gp.addMouseMotionPreprocessor(myTabActionsAutoHideListener, child);
244 myGlassPane = gp;
247 UIUtil.addAwtListener(new AWTEventListener() {
248 public void eventDispatched(final AWTEvent event) {
249 if (mySingleRowLayout.myMorePopup != null) return;
250 processFocusChange();
252 }, AWTEvent.FOCUS_EVENT_MASK, child);
254 myDragHelper = new DragHelper(child);
255 myDragHelper.start();
258 if (myProject != null && myFocusManager == IdeFocusManager.getGlobalInstance()) {
259 myFocusManager = IdeFocusManager.getInstance(myProject);
266 public JBTabs setNavigationActiondBinding(String prevActionId, String nextActionId) {
267 if (myNextAction != null) {
268 myNextAction.reconnect(nextActionId);
270 if (myPrevAction != null) {
271 myPrevAction.reconnect(prevActionId);
274 return this;
277 public JBTabs setNavigationActionsEnabled(boolean enabled) {
278 myNavigationActionsEnabled = enabled;
279 return this;
282 public final boolean isDisposed() {
283 return myDisposed;
286 public void dispose() {
287 myDisposed = true;
288 mySelectedInfo = null;
289 resetTabsCache();
290 myAttractions.clear();
291 myVisibleInfos.clear();
292 myUiDecorator = null;
293 myImage = null;
294 myActivePopup = null;
295 myInfo2Label.clear();
296 myInfo2Toolbar.clear();
297 myTabListeners.clear();
300 void resetTabsCache() {
301 myAllTabs = null;
304 private void processFocusChange() {
305 Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
306 if (owner == null) {
307 setFocused(false);
308 return;
311 if (owner == this || SwingUtilities.isDescendingFrom(owner, this)) {
312 setFocused(true);
314 else {
315 setFocused(false);
319 private void repaintAttractions() {
320 boolean needsUpdate = false;
321 for (TabInfo each : myVisibleInfos) {
322 TabLabel eachLabel = myInfo2Label.get(each);
323 needsUpdate |= eachLabel.repaintAttraction();
326 if (needsUpdate) {
327 relayout(true, false);
331 public void addNotify() {
332 super.addNotify();
333 addTimerUpdate();
336 public void removeNotify() {
337 super.removeNotify();
339 setFocused(false);
341 removeTimerUpdate();
343 if (myGlassPane != null) {
344 myGlassPane.removeMouseMotionPreprocessor(myTabActionsAutoHideListener);
345 myGlassPane = null;
349 private void addTimerUpdate() {
350 if (myActionManager != null && !myListenerAdded) {
351 myActionManager.addTimerListener(500, this);
352 myListenerAdded = true;
356 private void removeTimerUpdate() {
357 if (myActionManager != null && myListenerAdded) {
358 myActionManager.removeTimerListener(this);
359 myListenerAdded = false;
363 void setTestMode(final boolean testMode) {
364 myTestMode = testMode;
367 public void layoutComp(SingleRowPassInfo data, int deltaX, int deltaY, int deltaWidth, int deltaHeight) {
368 if (data.hToolbar != null) {
369 final int toolbarHeight = data.hToolbar.getPreferredSize().height;
370 final Rectangle compRect = layoutComp(deltaX, toolbarHeight + deltaY, data.comp, deltaWidth, deltaHeight);
371 layout(data.hToolbar, compRect.x, compRect.y - toolbarHeight, compRect.width, toolbarHeight);
373 else if (data.vToolbar != null) {
374 final int toolbarWidth = data.vToolbar.getPreferredSize().width;
375 final Rectangle compRect = layoutComp(toolbarWidth + deltaX, deltaY, data.comp, deltaWidth, deltaHeight);
376 layout(data.vToolbar, compRect.x - toolbarWidth, compRect.y, toolbarWidth, compRect.height);
378 else {
379 layoutComp(deltaX, deltaY, data.comp, deltaWidth, deltaHeight);
383 class TabActionsAutoHideListener extends MouseMotionAdapter {
385 private TabLabel myCurrentOverLabel;
386 private Point myLastOverPoint;
388 @Override
389 public void mouseMoved(final MouseEvent e) {
390 if (!myTabLabelActionsAutoHide) return;
392 final Point point = SwingUtilities.convertPoint(e.getComponent(), e.getX(), e.getY(), JBTabsImpl.this);
393 myLastOverPoint = point;
394 processMouseOver();
397 void processMouseOver() {
398 if (!myTabLabelActionsAutoHide) return;
400 if (myLastOverPoint == null) return;
402 if (myLastOverPoint.x >= 0 && myLastOverPoint.x < getWidth() && myLastOverPoint.y > 0 && myLastOverPoint.y < getHeight()) {
403 final TabLabel label = myInfo2Label.get(_findInfo(myLastOverPoint, true));
404 if (label != null) {
405 if (myCurrentOverLabel != null) {
406 myCurrentOverLabel.toggleShowActions(false);
408 label.toggleShowActions(true);
409 myCurrentOverLabel = label;
410 return;
414 if (myCurrentOverLabel != null) {
415 myCurrentOverLabel.toggleShowActions(false);
416 myCurrentOverLabel = null;
422 public ModalityState getModalityState() {
423 return ModalityState.stateForComponent(this);
426 public void run() {
427 updateTabActions(false);
430 public void updateTabActions(final boolean validateNow) {
431 final Ref<Boolean> changed = new Ref<Boolean>(Boolean.FALSE);
432 for (final TabInfo eachInfo : myInfo2Label.keySet()) {
433 updateTab(new Runnable() {
434 public void run() {
435 final boolean changes = myInfo2Label.get(eachInfo).updateTabActions();
436 changed.set(changed.get().booleanValue() || changes);
438 }, eachInfo);
441 if (changed.get().booleanValue()) {
442 if (validateNow) {
443 validate();
444 paintImmediately(0, 0, getWidth(), getHeight());
449 private void showMorePopup(final MouseEvent e) {
450 mySingleRowLayout.myMorePopup = new JPopupMenu();
451 for (final TabInfo each : myVisibleInfos) {
452 final JCheckBoxMenuItem item = new JCheckBoxMenuItem(each.getText());
453 mySingleRowLayout.myMorePopup.add(item);
454 if (getSelectedInfo() == each) {
455 item.setSelected(true);
457 item.addActionListener(new ActionListener() {
458 public void actionPerformed(final ActionEvent e) {
459 select(each, true);
464 mySingleRowLayout.myMorePopup.addPopupMenuListener(new PopupMenuListener() {
465 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
468 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
469 mySingleRowLayout.myMorePopup = null;
472 public void popupMenuCanceled(final PopupMenuEvent e) {
473 mySingleRowLayout.myMorePopup = null;
477 mySingleRowLayout.myMorePopup.show(this, e.getX(), e.getY());
481 private JComponent getToFocus() {
482 final TabInfo info = getSelectedInfo();
484 if (info == null) return null;
486 JComponent toFocus = null;
488 if (isRequestFocusOnLastFocusedComponent() && info.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) {
489 toFocus = info.getLastFocusOwner();
492 if (toFocus == null && info.getPreferredFocusableComponent() == null) {
493 return null;
497 if (toFocus == null) {
498 toFocus = info.getPreferredFocusableComponent();
499 final JComponent policyToFocus = myFocusManager.getFocusTargetFor(toFocus);
500 if (policyToFocus != null) {
501 toFocus = policyToFocus;
505 return toFocus;
508 public void requestFocus() {
509 final JComponent toFocus = getToFocus();
510 if (toFocus != null) {
511 toFocus.requestFocus();
513 else {
514 super.requestFocus();
518 public boolean requestFocusInWindow() {
519 final JComponent toFocus = getToFocus();
520 if (toFocus != null) {
521 return toFocus.requestFocusInWindow();
523 else {
524 return super.requestFocusInWindow();
528 private JBTabsImpl findTabs(Component c) {
529 Component eachParent = c;
530 while (eachParent != null) {
531 if (eachParent instanceof JBTabsImpl) {
532 return (JBTabsImpl)eachParent;
534 eachParent = eachParent.getParent();
537 return null;
541 @NotNull
542 public TabInfo addTab(TabInfo info, int index) {
543 if (getTabs().contains(info)) {
544 return getTabs().get(getTabs().indexOf(info));
547 info.getChangeSupport().addPropertyChangeListener(this);
548 final TabLabel label = new TabLabel(this, info);
549 myInfo2Label.put(info, label);
551 if (index < 0) {
552 myVisibleInfos.add(info);
554 else if (index > myVisibleInfos.size() - 1) {
555 myVisibleInfos.add(info);
557 else {
558 myVisibleInfos.add(index, info);
561 resetTabsCache();
564 updateText(info);
565 updateIcon(info);
566 updateSideComponent(info);
567 updateTabActions(info);
569 add(label);
571 adjust(info);
573 updateAll(false, false);
575 if (info.isHidden()) {
576 updateHiding();
579 if (getTabCount() == 1) {
580 fireBeforeSelectionChanged(null, info);
581 fireSelectionChanged(null, info);
584 return info;
588 @NotNull
589 public TabInfo addTab(TabInfo info) {
590 return addTab(info, -1);
593 public ActionGroup getPopupGroup() {
594 return myPopupGroup != null ? myPopupGroup.get() : null;
597 public String getPopupPlace() {
598 return myPopupPlace;
601 public JBTabs setPopupGroup(@NotNull final ActionGroup popupGroup, @NotNull String place, final boolean addNavigationGroup) {
602 return setPopupGroup(new Getter<ActionGroup>() {
603 public ActionGroup get() {
604 return popupGroup;
606 }, place, addNavigationGroup);
609 public JBTabs setPopupGroup(@NotNull final Getter<ActionGroup> popupGroup,
610 @NotNull final String place,
611 final boolean addNavigationGroup) {
612 myPopupGroup = popupGroup;
613 myPopupPlace = place;
614 myAddNavigationGroup = addNavigationGroup;
615 return this;
618 private void updateAll(final boolean forcedRelayout, final boolean now) {
619 mySelectedInfo = getSelectedInfo();
620 updateContainer(forcedRelayout, now);
621 removeDeferred();
622 updateListeners();
623 updateTabActions(false);
624 updateEnabling();
627 private boolean isMyChildIsFocusedNow() {
628 final Component owner = getFocusOwner();
629 if (owner == null) return false;
632 if (mySelectedInfo != null) {
633 if (!SwingUtilities.isDescendingFrom(owner, mySelectedInfo.getComponent())) return false;
636 return SwingUtilities.isDescendingFrom(owner, this);
639 @Nullable
640 private static JComponent getFocusOwner() {
641 final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
642 return (JComponent)(owner instanceof JComponent ? owner : null);
645 public ActionCallback select(@NotNull TabInfo info, boolean requestFocus) {
646 return _setSelected(info, requestFocus);
649 private ActionCallback _setSelected(final TabInfo info, final boolean requestFocus) {
650 if (mySelectedInfo != null && mySelectedInfo.equals(info)) {
651 if (!requestFocus) {
652 return new ActionCallback.Done();
654 else {
655 return requestFocus(getToFocus());
659 if (myRequestFocusOnLastFocusedComponent && mySelectedInfo != null) {
660 if (isMyChildIsFocusedNow()) {
661 mySelectedInfo.setLastFocusOwner(getFocusOwner());
665 TabInfo oldInfo = mySelectedInfo;
666 mySelectedInfo = info;
667 final TabInfo newInfo = getSelectedInfo();
669 fireBeforeSelectionChanged(oldInfo, newInfo);
671 updateContainer(false, true);
673 fireSelectionChanged(oldInfo, newInfo);
675 if (requestFocus) {
676 final JComponent toFocus = getToFocus();
677 if (myProject != null && toFocus != null) {
678 final ActionCallback result = new ActionCallback();
679 requestFocus(toFocus).doWhenProcessed(new Runnable() {
680 public void run() {
681 if (myDisposed) {
682 result.setRejected();
684 else {
685 removeDeferred().notifyWhenDone(result);
689 return result;
691 else {
692 requestFocus();
693 return removeDeferred();
696 else {
697 return removeDeferred();
701 private void fireBeforeSelectionChanged(TabInfo oldInfo, TabInfo newInfo) {
702 if (oldInfo != newInfo) {
703 for (TabsListener eachListener : myTabListeners) {
704 eachListener.beforeSelectionChanged(oldInfo, newInfo);
709 private void fireSelectionChanged(TabInfo oldInfo, TabInfo newInfo) {
710 if (oldInfo != newInfo) {
711 for (TabsListener eachListener : myTabListeners) {
712 if (eachListener != null) {
713 eachListener.selectionChanged(oldInfo, newInfo);
719 private ActionCallback requestFocus(final JComponent toFocus) {
720 if (toFocus == null) return new ActionCallback.Done();
722 if (myTestMode) {
723 toFocus.requestFocus();
724 return new ActionCallback.Done();
727 return myFocusManager.requestFocus(new FocusCommand.ByComponent(toFocus), true);
730 private ActionCallback removeDeferred() {
731 final ActionCallback callback = new ActionCallback();
733 final long executionRequest = ++myRemoveDefferredRequest;
735 final Runnable onDone = new Runnable() {
736 public void run() {
737 if (myRemoveDefferredRequest == executionRequest) {
738 removeDeferredNow();
741 callback.setDone();
745 myFocusManager.doWhenFocusSettlesDown(onDone);
747 return callback;
750 private void queueForRemove(Component c) {
751 if (c instanceof JComponent) {
752 addToDeferredRemove(c);
754 else {
755 remove(c);
759 private void unqueueFromRemove(Component c) {
760 myDeferredToRemove.remove(c);
763 private void removeDeferredNow() {
764 for (Component each : myDeferredToRemove.keySet()) {
765 if (each != null && each.getParent() == this) {
766 remove(each);
769 myDeferredToRemove.clear();
772 private void printRemoveInfo(final Component each) {
773 TabInfo removingInfo = null;
774 final List<TabInfo> all = getTabs();
775 for (TabInfo eachInfo : all) {
776 if (eachInfo.getComponent() == each) {
777 removingInfo = eachInfo;
778 break;
782 //System.out.println(" - removing " + (removingInfo != null ? " component for " + removingInfo : each));
785 public void propertyChange(final PropertyChangeEvent evt) {
786 final TabInfo tabInfo = (TabInfo)evt.getSource();
787 if (TabInfo.ACTION_GROUP.equals(evt.getPropertyName())) {
788 updateSideComponent(tabInfo);
789 relayout(false, false);
791 else if (TabInfo.COMPONENT.equals(evt.getPropertyName())) {
792 relayout(true, false);
794 else if (TabInfo.TEXT.equals(evt.getPropertyName())) {
795 updateText(tabInfo);
797 else if (TabInfo.ICON.equals(evt.getPropertyName())) {
798 updateIcon(tabInfo);
800 else if (TabInfo.TAB_COLOR.equals(evt.getPropertyName())) {
801 updateColor(tabInfo);
803 else if (TabInfo.ALERT_STATUS.equals(evt.getPropertyName())) {
804 boolean start = ((Boolean)evt.getNewValue()).booleanValue();
805 updateAttraction(tabInfo, start);
807 else if (TabInfo.TAB_ACTION_GROUP.equals(evt.getPropertyName())) {
808 updateTabActions(tabInfo);
809 relayout(false, false);
811 else if (TabInfo.HIDDEN.equals(evt.getPropertyName())) {
812 updateHiding();
813 relayout(false, false);
815 else if (TabInfo.ENABLED.equals(evt.getPropertyName())) {
816 updateEnabling();
820 private void updateEnabling() {
821 final List<TabInfo> all = getTabs();
822 for (TabInfo each : all) {
823 final TabLabel eachLabel = myInfo2Label.get(each);
824 eachLabel.setTabEnabled(each.isEnabled());
827 final TabInfo selected = getSelectedInfo();
828 if (selected != null && !selected.isEnabled()) {
829 final TabInfo toSelect = getToSelectOnRemoveOf(selected);
830 if (toSelect != null) {
831 select(toSelect, myFocusManager.getFocusedDescendantFor(this) != null);
836 private void updateHiding() {
837 boolean update = false;
839 Iterator<TabInfo> visible = myVisibleInfos.iterator();
840 while (visible.hasNext()) {
841 TabInfo each = visible.next();
842 if (each.isHidden() && !myHiddenInfos.containsKey(each)) {
843 myHiddenInfos.put(each, myVisibleInfos.indexOf(each));
844 visible.remove();
845 update = true;
850 Iterator<TabInfo> hidden = myHiddenInfos.keySet().iterator();
851 while (hidden.hasNext()) {
852 TabInfo each = hidden.next();
853 if (!each.isHidden() && myHiddenInfos.containsKey(each)) {
854 myVisibleInfos.add(getIndexInVisibleArray(each), each);
855 hidden.remove();
856 update = true;
861 if (update) {
862 resetTabsCache();
863 if (mySelectedInfo != null && myHiddenInfos.containsKey(mySelectedInfo)) {
864 mySelectedInfo = getToSelectOnRemoveOf(mySelectedInfo);
866 updateAll(true, false);
870 private int getIndexInVisibleArray(TabInfo each) {
871 Integer index = myHiddenInfos.get(each);
872 if (index == null) {
873 index = Integer.valueOf(myVisibleInfos.size());
876 if (index > myVisibleInfos.size()) {
877 index = myVisibleInfos.size();
880 if (index.intValue() < 0) {
881 index = 0;
884 return index.intValue();
887 private void updateIcon(final TabInfo tabInfo) {
888 updateTab(new Runnable() {
889 public void run() {
890 myInfo2Label.get(tabInfo).setIcon(tabInfo.getIcon());
892 }, tabInfo);
895 private void updateColor(final TabInfo tabInfo) {
896 myInfo2Label.get(tabInfo).setInactiveStateImage(null);
898 updateTab(new Runnable() {
899 public void run() {
900 repaint();
902 }, tabInfo);
905 private void updateTab(Runnable update, TabInfo info) {
906 final TabLabel label = myInfo2Label.get(info);
907 update.run();
908 if (label.getRootPane() != null) {
909 if (label.isValid()) {
910 label.repaint();
912 else {
913 revalidateAndRepaint(false);
918 void revalidateAndRepaint(final boolean layoutNow) {
920 if (myVisibleInfos.isEmpty()) {
921 setOpaque(false);
922 final Component nonOpaque = UIUtil.findUltimateParent(this);
923 if (nonOpaque != null && getParent() != null) {
924 final Rectangle toRepaint = SwingUtilities.convertRectangle(getParent(), getBounds(), nonOpaque);
925 nonOpaque.repaint(toRepaint.x, toRepaint.y, toRepaint.width, toRepaint.height);
928 else {
929 setOpaque(true);
932 if (layoutNow) {
933 validate();
935 else {
936 revalidate();
939 repaint();
943 private void updateAttraction(final TabInfo tabInfo, boolean start) {
944 if (start) {
945 myAttractions.add(tabInfo);
947 else {
948 myAttractions.remove(tabInfo);
949 tabInfo.setBlinkCount(0);
952 if (start && !myAnimator.isRunning()) {
953 myAnimator.resume();
955 else if (!start && myAttractions.isEmpty()) {
956 myAnimator.suspend();
957 repaintAttractions();
961 private void updateText(final TabInfo tabInfo) {
962 updateTab(new Runnable() {
963 public void run() {
964 final TabLabel label = myInfo2Label.get(tabInfo);
965 label.setText(tabInfo.getColoredText());
966 label.setToolTipText(tabInfo.getTooltipText());
968 }, tabInfo);
971 private void updateSideComponent(final TabInfo tabInfo) {
972 final Toolbar old = myInfo2Toolbar.get(tabInfo);
973 if (old != null) {
974 remove(old);
977 final Toolbar toolbar = createToolbarComponent(tabInfo);
978 myInfo2Toolbar.put(tabInfo, toolbar);
979 add(toolbar);
982 private void updateTabActions(final TabInfo info) {
983 myInfo2Label.get(info).setTabActions(info.getTabLabelActions());
986 @Nullable
987 public TabInfo getSelectedInfo() {
988 if (!myVisibleInfos.contains(mySelectedInfo)) {
989 mySelectedInfo = null;
991 return mySelectedInfo != null ? mySelectedInfo : !myVisibleInfos.isEmpty() ? myVisibleInfos.get(0) : null;
994 @Nullable
995 private TabInfo getToSelectOnRemoveOf(TabInfo info) {
996 if (!myVisibleInfos.contains(info)) return null;
997 if (mySelectedInfo != info) return null;
999 if (myVisibleInfos.size() == 1) return null;
1001 int index = myVisibleInfos.indexOf(info);
1003 TabInfo result = null;
1004 if (index > 0) {
1005 result = findEnabledBackward(index - 1);
1008 if (result == null) {
1009 result = findEnabledForward(index + 1);
1012 return result;
1015 private TabInfo findEnabledForward(int from) {
1016 int index = from;
1017 while (index < myVisibleInfos.size() && index >= 0) {
1018 final TabInfo each = myVisibleInfos.get(index);
1019 if (each.isEnabled()) return each;
1020 index++;
1023 return null;
1026 private TabInfo findEnabledBackward(int from) {
1027 int index = from;
1028 while (index >= 0 && from < myVisibleInfos.size()) {
1029 final TabInfo each = myVisibleInfos.get(index);
1030 if (each.isEnabled()) return each;
1031 index--;
1034 return null;
1037 protected Toolbar createToolbarComponent(final TabInfo tabInfo) {
1038 return new Toolbar(this, tabInfo);
1041 @NotNull
1042 public TabInfo getTabAt(final int tabIndex) {
1043 return getTabs().get(tabIndex);
1046 @NotNull
1047 public List<TabInfo> getTabs() {
1048 if (myAllTabs != null) return myAllTabs;
1050 ArrayList<TabInfo> result = new ArrayList<TabInfo>();
1051 result.addAll(myVisibleInfos);
1053 for (TabInfo each : myHiddenInfos.keySet()) {
1054 result.add(getIndexInVisibleArray(each), each);
1057 myAllTabs = result;
1059 return result;
1062 public TabInfo getTargetInfo() {
1063 return myPopupInfo != null ? myPopupInfo : getSelectedInfo();
1066 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
1069 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
1070 resetPopup();
1073 public void popupMenuCanceled(final PopupMenuEvent e) {
1074 resetPopup();
1077 private void resetPopup() {
1078 //todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled
1079 SwingUtilities.invokeLater(new Runnable() {
1080 public void run() {
1081 myPopupInfo = null;
1086 public void setPaintBlocked(boolean blocked, final boolean takeSnapshot) {
1087 if (blocked && !myPaintBlocked) {
1088 if (takeSnapshot) {
1089 myImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
1090 final Graphics2D g = myImage.createGraphics();
1091 super.paint(g);
1092 g.dispose();
1096 myPaintBlocked = blocked;
1098 if (!myPaintBlocked) {
1099 if (myImage != null) {
1100 myImage.flush();
1103 myImage = null;
1104 repaint();
1109 private void addToDeferredRemove(final Component c) {
1110 if (!myDeferredToRemove.containsKey(c)) {
1111 myDeferredToRemove.put(c, c);
1115 public boolean isToDrawBorderIfTabsHidden() {
1116 return myToDrawBorderIfTabsHidden;
1119 @NotNull
1120 public JBTabsPresentation setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden) {
1121 myToDrawBorderIfTabsHidden = toDrawBorderIfTabsHidden;
1122 return this;
1125 @NotNull
1126 public JBTabs getJBTabs() {
1127 return this;
1130 public static class Toolbar extends JPanel {
1131 private final JBTabsImpl myTabs;
1132 private final TabInfo myInfo;
1134 public Toolbar(JBTabsImpl tabs, TabInfo info) {
1135 myTabs = tabs;
1136 myInfo = info;
1138 setLayout(new BorderLayout());
1140 final ActionGroup group = info.getGroup();
1141 final JComponent side = info.getSideComponent();
1143 if (group != null && myTabs.myActionManager != null) {
1144 final String place = info.getPlace();
1145 ActionToolbar toolbar =
1146 myTabs.myActionManager.createActionToolbar(place != null ? place : ActionPlaces.UNKNOWN, group, myTabs.myHorizontalSide);
1147 toolbar.setTargetComponent(info.getActionsContextComponent());
1148 final JComponent actionToolbar = toolbar.getComponent();
1149 add(actionToolbar, BorderLayout.CENTER);
1152 if (side != null) {
1153 if (group != null) {
1154 add(side, BorderLayout.EAST);
1156 else {
1157 add(side, BorderLayout.CENTER);
1162 public boolean isEmpty() {
1163 return getComponentCount() == 0;
1168 public void doLayout() {
1169 try {
1170 myHeaderFitSize = computeHeaderFitSize();
1172 final Collection<TabLabel> labels = myInfo2Label.values();
1173 for (TabLabel each : labels) {
1174 each.setTabActionsAutoHide(myTabLabelActionsAutoHide);
1178 if (isSingleRow()) {
1179 myLastLayoutPass = mySingleRowLayout.layoutSingleRow();
1180 myTableLayout.myLastTableLayout = null;
1182 else {
1183 myLastLayoutPass = myTableLayout.layoutTable();
1184 mySingleRowLayout.myLastSingRowLayout = null;
1187 if (isStealthModeEffective() && !isHideTabs()) {
1188 final TabLabel label = getSelectedLabel();
1189 final Rectangle bounds = label.getBounds();
1190 final Insets insets = getLayoutInsets();
1191 layout(label, insets.left, bounds.y, getWidth() - insets.right - insets.left, bounds.height);
1195 moveDraggedTabLabel();
1197 myTabActionsAutoHideListener.processMouseOver();
1199 finally {
1200 myForcedRelayout = false;
1203 applyResetComponents();
1206 void moveDraggedTabLabel() {
1207 if (myDragHelper != null && myDragHelper.myDragRec != null) {
1208 final TabLabel selectedLabel = myInfo2Label.get(getSelectedInfo());
1209 if (selectedLabel != null) {
1210 final Rectangle bounds = selectedLabel.getBounds();
1211 if (isHorizontalTabs()) {
1212 selectedLabel.setBounds(myDragHelper.myDragRec.x, bounds.y, bounds.width, bounds.height);
1214 else {
1215 selectedLabel.setBounds(bounds.x, myDragHelper.myDragRec.y, bounds.width, bounds.height);
1221 private Dimension computeHeaderFitSize() {
1222 final Max max = computeMaxSize();
1224 if (myPosition == JBTabsPosition.top || myPosition == JBTabsPosition.bottom) {
1225 return new Dimension(getSize().width, myHorizontalSide ? Math.max(max.myLabel.height, max.myToolbar.height) : max.myLabel.height);
1227 else {
1228 return new Dimension(max.myLabel.width + (myHorizontalSide ? 0 : max.myToolbar.width), getSize().height);
1232 public Rectangle layoutComp(int componentX, int componentY, final JComponent comp, int deltaWidth, int deltaHeight) {
1233 final Insets insets = getLayoutInsets();
1235 final Insets border = isHideTabs() ? new Insets(0, 0, 0, 0) : myBorder.getEffectiveBorder();
1236 final boolean noTabsVisible = isStealthModeEffective() || isHideTabs();
1238 if (noTabsVisible) {
1239 border.top = getBorder(-1);
1240 border.bottom = getBorder(-1);
1241 border.left = getBorder(-1);
1242 border.right = getBorder(-1);
1245 final Insets inner = getInnerInsets();
1246 border.top += inner.top;
1247 border.bottom += inner.bottom;
1248 border.left += inner.left;
1249 border.right += inner.right;
1252 int x = insets.left + componentX + border.left;
1253 int y = insets.top + componentY + border.top;
1254 int width = getWidth() - insets.left - insets.right - componentX - border.left - border.right;
1255 int height = getHeight() - insets.top - insets.bottom - componentY - border.top - border.bottom;
1257 if (!noTabsVisible) {
1258 width += deltaWidth;
1259 height += deltaHeight;
1262 return layout(comp, x, y, width, height);
1266 public JBTabsPresentation setInnerInsets(final Insets innerInsets) {
1267 myInnerInsets = innerInsets;
1268 return this;
1271 public Insets getInnerInsets() {
1272 return myInnerInsets;
1275 public Insets getLayoutInsets() {
1276 Insets insets = getInsets();
1277 if (insets == null) {
1278 insets = new Insets(0, 0, 0, 0);
1280 return insets;
1283 private int fixInset(int inset, int addin) {
1284 return inset + addin;
1288 public int getToolbarInset() {
1289 return getArcSize() + 1;
1292 public void resetLayout(boolean resetLabels) {
1293 if (resetLabels) {
1294 mySingleRowLayout.myLeftGhost.reset();
1295 mySingleRowLayout.myRightGhost.reset();
1298 for (TabInfo each : myVisibleInfos) {
1299 reset(each, resetLabels);
1302 for (TabInfo each : myHiddenInfos.keySet()) {
1303 reset(each, resetLabels);
1306 for (Component eachDeferred : myDeferredToRemove.keySet()) {
1307 resetLayout((JComponent)eachDeferred);
1311 private void reset(final TabInfo each, final boolean resetLabels) {
1312 final JComponent c = each.getComponent();
1313 if (c != null) {
1314 resetLayout(c);
1317 resetLayout(myInfo2Toolbar.get(each));
1319 if (resetLabels) {
1320 resetLayout(myInfo2Label.get(each));
1325 private int getArcSize() {
1326 return 4;
1329 public int getGhostTabLength() {
1330 return 15;
1334 protected void paintComponent(final Graphics g) {
1335 super.paintComponent(g);
1337 if (myVisibleInfos.isEmpty()) return;
1339 Graphics2D g2d = (Graphics2D)g;
1341 final GraphicsConfig config = new GraphicsConfig(g2d);
1342 config.setAntialiasing(true);
1345 g2d.setColor(getBackground());
1346 final Rectangle clip = g2d.getClipBounds();
1347 g2d.fillRect(clip.x, clip.y, clip.width, clip.height);
1349 final TabInfo selected = getSelectedInfo();
1351 boolean leftGhostExists = isSingleRow();
1352 boolean rightGhostExists = isSingleRow();
1354 if (!isStealthModeEffective() && !isHideTabs()) {
1355 if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.lastGhostVisible) {
1356 paintLastGhost(g2d);
1360 paintNonSelectedTabs(g2d, leftGhostExists);
1362 if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.firstGhostVisible) {
1363 paintFirstGhost(g2d);
1368 config.setAntialiasing(false);
1370 if (isSideComponentVertical()) {
1371 Toolbar toolbarComp = myInfo2Toolbar.get(mySelectedInfo);
1372 if (toolbarComp != null && !toolbarComp.isEmpty()) {
1373 Rectangle toolBounds = toolbarComp.getBounds();
1374 g2d.setColor(CaptionPanel.CNT_ACTIVE_COLOR);
1375 g2d.drawLine((int)toolBounds.getMaxX(), toolBounds.y, (int)toolBounds.getMaxX(), (int)toolBounds.getMaxY() - 1);
1379 config.restore();
1382 private Color getActiveTabColor(final Color c) {
1383 final TabInfo info = getSelectedInfo();
1384 if (info == null) {
1385 return c;
1388 final Color tabColor = info.getTabColor();
1389 return tabColor == null ? c : tabColor;
1392 private void paintSelectionAndBorder(Graphics2D g2d) {
1393 if (getSelectedLabel() == null) return;
1395 final ShapeInfo shapeInfo = computeSelectedLabelShape();
1396 if (!isHideTabs()) {
1397 g2d.setColor(getBackground());
1398 g2d.fill(shapeInfo.fillPath.getShape());
1401 final int alpha;
1402 int paintTopY = shapeInfo.labelTopY;
1403 int paintBottomY = shapeInfo.labelBottomY;
1404 final boolean paintFocused = myPaintFocus && (myFocused || myActivePopup != null);
1405 Color bgPreFill = null;
1406 if (paintFocused) {
1407 final Color bgColor = getActiveTabColor(getActiveTabFillIn());
1408 if (bgColor == null) {
1409 shapeInfo.from = UIUtil.getFocusedFillColor();
1410 shapeInfo.to = UIUtil.getFocusedFillColor();
1412 else {
1413 bgPreFill = bgColor;
1414 alpha = 255;
1415 paintBottomY = shapeInfo.labelTopY + shapeInfo.labelPath.deltaY(getArcSize() - 2);
1416 shapeInfo.from = UIUtil.toAlpha(UIUtil.getFocusedFillColor(), alpha);
1417 shapeInfo.to = UIUtil.toAlpha(getActiveTabFillIn(), alpha);
1420 else {
1421 final Color bgColor = getActiveTabColor(getActiveTabFillIn());
1422 if (isPaintFocus()) {
1423 if (bgColor == null) {
1424 alpha = 150;
1425 shapeInfo.from = UIUtil.toAlpha(UIUtil.getPanelBackgound().brighter(), alpha);
1426 shapeInfo.to = UIUtil.toAlpha(UIUtil.getPanelBackgound(), alpha);
1428 else {
1429 alpha = 255;
1430 shapeInfo.from = UIUtil.toAlpha(bgColor, alpha);
1431 shapeInfo.to = UIUtil.toAlpha(bgColor, alpha);
1434 else {
1435 alpha = 255;
1436 final Color tabColor = getActiveTabColor(null);
1437 shapeInfo.from = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha);
1438 shapeInfo.to = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha);
1442 if (!isHideTabs()) {
1443 if (bgPreFill != null) {
1444 g2d.setColor(bgPreFill);
1445 g2d.fill(shapeInfo.fillPath.getShape());
1448 final Line2D.Float gradientLine =
1449 shapeInfo.fillPath.transformLine(shapeInfo.fillPath.getX(), paintTopY, shapeInfo.fillPath.getX(), paintBottomY);
1452 g2d.setPaint(new GradientPaint((float)gradientLine.getX1(), (float)gradientLine.getY1(),
1453 shapeInfo.fillPath.transformY1(shapeInfo.from, shapeInfo.to), (float)gradientLine.getX2(),
1454 (float)gradientLine.getY2(), shapeInfo.fillPath.transformY1(shapeInfo.to, shapeInfo.from)));
1455 g2d.fill(shapeInfo.fillPath.getShape());
1458 final Color tabColor = getActiveTabColor(null);
1459 Color borderColor = tabColor == null ? UIUtil.getBoundsColor(paintFocused) : tabColor.darker();
1460 g2d.setColor(borderColor);
1462 if (!isHideTabs()) {
1463 g2d.draw(shapeInfo.path.getShape());
1466 paintBorder(g2d, shapeInfo, borderColor);
1469 private ShapeInfo computeSelectedLabelShape() {
1470 final ShapeInfo shape = new ShapeInfo();
1472 shape.path = getEffectiveLayout().createShapeTransform(getSize());
1473 shape.insets = shape.path.transformInsets(getLayoutInsets());
1474 shape.labelPath = shape.path.createTransform(getSelectedLabel().getBounds());
1476 shape.labelBottomY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
1477 shape.labelTopY = shape.labelPath.getY();
1478 shape.labelLeftX = shape.labelPath.getX();
1479 shape.labelRightX = shape.labelPath.getX() + shape.labelPath.deltaX(shape.labelPath.getWidth());
1481 shape.path.moveTo(shape.insets.left, shape.labelBottomY);
1482 shape.path.lineTo(shape.labelLeftX, shape.labelBottomY);
1483 shape.path.lineTo(shape.labelLeftX, shape.labelTopY + shape.labelPath.deltaY(getArcSize()));
1484 shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getArcSize()), shape.labelTopY);
1486 int lastX = shape.path.getWidth() - shape.path.deltaX(shape.insets.right + 1);
1488 if (isStealthModeEffective()) {
1489 shape.path.lineTo(lastX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1490 shape.path.quadTo(lastX, shape.labelTopY, lastX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1491 shape.path.lineTo(lastX, shape.labelBottomY);
1493 else {
1494 shape.path.lineTo(shape.labelRightX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1495 shape.path.quadTo(shape.labelRightX, shape.labelTopY, shape.labelRightX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1496 if (myLastLayoutPass.hasCurveSpaceFor(getSelectedInfo())) {
1497 shape.path.lineTo(shape.labelRightX, shape.labelBottomY - shape.path.deltaY(getArcSize()));
1498 shape.path.quadTo(shape.labelRightX, shape.labelBottomY, shape.labelRightX + shape.path.deltaX(getArcSize()), shape.labelBottomY);
1500 else {
1501 shape.path.lineTo(shape.labelRightX, shape.labelBottomY);
1505 shape.path.lineTo(lastX, shape.labelBottomY);
1507 if (isStealthModeEffective()) {
1508 shape.path.closePath();
1511 shape.fillPath = shape.path.copy();
1512 if (!isHideTabs()) {
1513 shape.fillPath.lineTo(lastX, shape.labelBottomY + shape.fillPath.deltaY(1));
1514 shape.fillPath.lineTo(shape.labelLeftX, shape.labelBottomY + shape.fillPath.deltaY(1));
1515 shape.fillPath.closePath();
1517 return shape;
1520 private TabLabel getSelectedLabel() {
1521 return myInfo2Label.get(getSelectedInfo());
1524 static class ShapeInfo {
1525 ShapeTransform path;
1526 ShapeTransform fillPath;
1527 ShapeTransform labelPath;
1528 int labelBottomY;
1529 int labelTopY;
1530 int labelLeftX;
1531 int labelRightX;
1532 Insets insets;
1533 Color from;
1534 Color to;
1538 private void paintFirstGhost(Graphics2D g2d) {
1539 final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.firstGhost);
1541 int topX = path.getX() + path.deltaX(getCurveArc());
1542 int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1543 int bottomX = path.getMaxX() + path.deltaX(1);
1544 int bottomY = path.getMaxY() + path.deltaY(1);
1546 path.moveTo(topX, topY);
1548 final boolean isLeftFromSelection = mySingleRowLayout.myLastSingRowLayout.toLayout.indexOf(getSelectedInfo()) == 0;
1550 if (isLeftFromSelection) {
1551 path.lineTo(bottomX, topY);
1553 else {
1554 path.lineTo(bottomX - getArcSize(), topY);
1555 path.quadTo(bottomX, topY, bottomX, topY + path.deltaY(getArcSize()));
1558 path.lineTo(bottomX, bottomY);
1559 path.lineTo(topX, bottomY);
1561 path.quadTo(topX - path.deltaX(getCurveArc() * 2 - 1), bottomY - path.deltaY(Math.abs(bottomY - topY) / 4), topX,
1562 bottomY - path.deltaY(Math.abs(bottomY - topY) / 2));
1564 path.quadTo(topX + path.deltaX(getCurveArc() - 1), topY + path.deltaY(Math.abs(bottomY - topY) / 4), topX, topY);
1566 path.closePath();
1568 g2d.setColor(getBackground());
1569 g2d.fill(path.getShape());
1571 g2d.setColor(getBoundsColor());
1572 g2d.draw(path.getShape());
1574 g2d.setColor(getTopBlickColor());
1575 g2d.drawLine(topX + path.deltaX(1), topY + path.deltaY(1), bottomX - path.deltaX(getArcSize()), topY + path.deltaY(1));
1577 g2d.setColor(getRightBlockColor());
1578 g2d.drawLine(bottomX - path.deltaX(1), topY + path.deltaY(getArcSize()), bottomX - path.deltaX(1), bottomY - path.deltaY(1));
1581 private void paintLastGhost(Graphics2D g2d) {
1582 final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.lastGhost);
1584 int topX = path.getX() - path.deltaX(getArcSize());
1585 int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1586 int bottomX = path.getMaxX() - path.deltaX(getCurveArc());
1587 int bottomY = path.getMaxY() + path.deltaY(1);
1589 path.moveTo(topX, topY);
1590 path.lineTo(bottomX, topY);
1591 path.quadTo(bottomX - getCurveArc(), topY + (bottomY - topY) / 4, bottomX, topY + (bottomY - topY) / 2);
1592 path.quadTo(bottomX + getCurveArc(), bottomY - (bottomY - topY) / 4, bottomX, bottomY);
1593 path.lineTo(topX, bottomY);
1595 path.closePath();
1597 g2d.setColor(getBackground());
1598 g2d.fill(path.getShape());
1600 g2d.setColor(getBoundsColor());
1601 g2d.draw(path.getShape());
1603 g2d.setColor(getTopBlickColor());
1604 g2d.drawLine(topX, topY + path.deltaY(1), bottomX - path.deltaX(getCurveArc()), topY + path.deltaY(1));
1607 private int getCurveArc() {
1608 return 2;
1611 private Color getBoundsColor() {
1612 return Color.gray;
1615 private Color getRightBlockColor() {
1616 return Color.lightGray;
1619 private Color getTopBlickColor() {
1620 return Color.white;
1623 private void paintNonSelectedTabs(final Graphics2D g2d, final boolean leftGhostExists) {
1624 TabInfo selected = getSelectedInfo();
1625 if (myLastPaintedSelection == null || !myLastPaintedSelection.equals(selected)) {
1626 List<TabInfo> tabs = getTabs();
1627 for (TabInfo each : tabs) {
1628 myInfo2Label.get(each).setInactiveStateImage(null);
1632 for (int eachRow = 0; eachRow < myLastLayoutPass.getRowCount(); eachRow++) {
1633 for (int eachColumn = myLastLayoutPass.getColumnCount(eachRow) - 1; eachColumn >= 0; eachColumn--) {
1634 final TabInfo each = myLastLayoutPass.getTabAt(eachRow, eachColumn);
1635 if (getSelectedInfo() == each) {
1636 continue;
1638 paintNonSelected(g2d, each, leftGhostExists);
1642 myLastPaintedSelection = selected;
1645 private void paintNonSelected(final Graphics2D g2d, final TabInfo each, final boolean leftGhostExists) {
1646 final TabLabel label = myInfo2Label.get(each);
1647 if (label.getBounds().width == 0) return;
1649 int imageInsets = getArcSize() + 1;
1651 Rectangle bounds = label.getBounds();
1653 int x = bounds.x - imageInsets;
1654 int y = bounds.y;
1655 int width = bounds.width + imageInsets * 2 + 1;
1656 int height = bounds.height + getArcSize() + 1;
1658 if (isToBufferPainting()) {
1659 BufferedImage img = label.getInactiveStateImage(bounds);
1661 if (img == null) {
1662 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
1663 Graphics2D imgG2d = img.createGraphics();
1664 imgG2d.addRenderingHints(g2d.getRenderingHints());
1665 doPaintInactictive(imgG2d, leftGhostExists, label, new Rectangle(imageInsets, 0, label.getWidth(), label.getHeight()));
1666 imgG2d.dispose();
1669 g2d.drawImage(img, x, y, width, height, null);
1671 label.setInactiveStateImage(img);
1672 } else {
1673 doPaintInactictive(g2d, leftGhostExists, label, label.getBounds());
1674 label.setInactiveStateImage(null);
1678 private boolean isToBufferPainting() {
1679 return Registry.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint;
1682 private void doPaintInactictive(Graphics2D g2d, boolean leftGhostExists, TabLabel label, Rectangle effectiveBounds) {
1683 int tabIndex = myVisibleInfos.indexOf(label.getInfo());
1685 final int arc = getArcSize();
1686 Color topBlickColor = getTopBlickColor();
1687 Color rightBlockColor = getRightBlockColor();
1688 Color boundsColor = getBoundsColor();
1689 Color backgroundColor = getBackground();
1691 final Color tabColor = label.getInfo().getTabColor();
1692 if (tabColor != null) {
1693 backgroundColor = tabColor;
1694 boundsColor = tabColor.darker();
1695 topBlickColor = tabColor.brighter().brighter();
1696 rightBlockColor = tabColor;
1699 final TabInfo selected = getSelectedInfo();
1700 final int selectionTabVShift = getSelectionTabVShift();
1703 final TabInfo prev = myLastLayoutPass.getPreviousFor(myVisibleInfos.get(tabIndex));
1704 final TabInfo next = myLastLayoutPass.getNextFor(myVisibleInfos.get(tabIndex));
1707 boolean firstShowing = prev == null;
1708 if (!firstShowing && !leftGhostExists) {
1709 firstShowing = myInfo2Label.get(prev).getBounds().width == 0;
1712 boolean lastShowing = next == null;
1713 if (!lastShowing) {
1714 lastShowing = myInfo2Label.get(next).getBounds().width == 0;
1717 boolean leftFromSelection = selected != null && tabIndex == myVisibleInfos.indexOf(selected) - 1;
1719 Rectangle originalBounds = effectiveBounds;
1720 final ShapeTransform shape = getEffectiveLayout().createShapeTransform(originalBounds);
1722 int leftX = firstShowing ? shape.getX() : shape.getX() - shape.deltaX(arc + 1);
1723 int topY = shape.getY() + shape.deltaY(selectionTabVShift);
1724 int rigthX = !lastShowing && leftFromSelection ? shape.getMaxX() + shape.deltaX(arc + 1) : shape.getMaxX();
1725 int bottomY = shape.getMaxY() + shape.deltaY(1);
1727 shape.moveTo(leftX, bottomY);
1728 shape.lineTo(leftX, topY + shape.deltaY(arc));
1729 shape.quadTo(leftX, topY, leftX + shape.deltaX(arc), topY);
1730 shape.lineTo(rigthX - shape.deltaX(arc), topY);
1731 shape.quadTo(rigthX, topY, rigthX, topY + shape.deltaY(arc));
1732 shape.lineTo(rigthX, bottomY);
1734 if (!isSingleRow()) {
1735 final TablePassInfo info = myTableLayout.myLastTableLayout;
1736 if (!info.isInSelectionRow(label.getInfo())) {
1737 shape.lineTo(rigthX, bottomY + shape.deltaY(getArcSize()));
1738 shape.lineTo(leftX, bottomY + shape.deltaY(getArcSize()));
1739 shape.lineTo(leftX, bottomY);
1743 shape.closePath();
1745 g2d.setColor(backgroundColor);
1746 g2d.fill(shape.getShape());
1748 // TODO
1750 final Line2D.Float gradientLine =
1751 shape.transformLine(0, topY, 0, topY + shape.deltaY((int) (shape.getHeight() / 1.5 )));
1753 final GradientPaint gp =
1754 new GradientPaint(gradientLine.x1, gradientLine.y1,
1755 shape.transformY1(backgroundColor.brighter().brighter(), backgroundColor),
1756 gradientLine.x2, gradientLine.y2,
1757 shape.transformY1(backgroundColor, backgroundColor.brighter().brighter()));
1758 final Paint old = g2d.getPaint();
1759 g2d.setPaint(gp);
1760 g2d.fill(shape.getShape());
1761 g2d.setPaint(old);
1763 g2d.setColor(topBlickColor);
1764 g2d.draw(
1765 shape.transformLine(leftX + shape.deltaX(arc + 1), topY + shape.deltaY(1), rigthX - shape.deltaX(arc - 1), topY + shape.deltaY(1)));
1767 g2d.setColor(rightBlockColor);
1768 g2d.draw(shape.transformLine(rigthX - shape.deltaX(1), topY + shape.deltaY(arc - 1), rigthX - shape.deltaX(1), bottomY));
1770 g2d.setColor(boundsColor);
1771 g2d.draw(shape.getShape());
1774 public int getSelectionTabVShift() {
1775 return 2;
1778 private void paintBorder(Graphics2D g2d, ShapeInfo shape, final Color borderColor) {
1780 final ShapeTransform shaper = shape.path.copy().reset();
1782 final Insets paintBorder = shape.path.transformInsets(myBorder.getEffectiveBorder());
1784 int topY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
1786 int bottomY = topY + paintBorder.top - 2;
1787 int middleY = topY + (bottomY - topY) / 2;
1790 final int boundsX = shape.path.getX() + shape.path.deltaX(shape.insets.left);
1792 final int boundsY =
1793 isHideTabs() ? shape.path.getY() + shape.path.deltaY(shape.insets.top) : shape.labelPath.getMaxY() + shape.path.deltaY(1);
1795 final int boundsHeight = Math.abs(shape.path.getMaxY() - boundsY) - shape.insets.bottom - paintBorder.bottom;
1796 final int boundsWidth = Math.abs(shape.path.getMaxX() - (shape.insets.left + shape.insets.right));
1798 if (paintBorder.top > 0) {
1799 if (isHideTabs()) {
1800 if (isToDrawBorderIfTabsHidden()) {
1801 g2d.setColor(borderColor);
1802 g2d.fill(shaper.reset().doRect(boundsX, boundsY, boundsWidth, 1).getShape());
1805 else {
1806 Color tabFillColor = getActiveTabColor(null);
1807 if (tabFillColor == null) {
1808 tabFillColor = shape.path.transformY1(shape.to, shape.from);
1811 g2d.setColor(tabFillColor);
1812 g2d.fill(shaper.reset().doRect(boundsX, topY + shape.path.deltaY(1), boundsWidth, paintBorder.top - 1).getShape());
1814 g2d.setColor(borderColor);
1815 if (paintBorder.top == 2) {
1816 final Line2D.Float line = shape.path.transformLine(boundsX, topY, boundsX + shape.path.deltaX(boundsWidth - 1), topY);
1818 g2d.drawLine((int)line.x1, (int)line.y1, (int)line.x2, (int)line.y2);
1820 else if (paintBorder.top > 2) {
1821 //todo kirillk
1822 //start hack
1823 int deltaY = 0;
1824 if (myPosition == JBTabsPosition.bottom || myPosition == JBTabsPosition.right) {
1825 deltaY = 1;
1827 //end hack
1828 final int topLine = topY + shape.path.deltaY(paintBorder.top - 1);
1829 g2d.fill(shaper.reset().doRect(boundsX, topLine + deltaY, boundsWidth - 1, 1).getShape());
1834 g2d.setColor(borderColor);
1836 //bottom
1837 g2d.fill(shaper.reset().doRect(boundsX, Math.abs(shape.path.getMaxY() - shape.insets.bottom - paintBorder.bottom), boundsWidth,
1838 paintBorder.bottom).getShape());
1840 //left
1841 g2d.fill(shaper.reset().doRect(boundsX, boundsY, paintBorder.left, boundsHeight).getShape());
1843 //right
1844 g2d.fill(shaper.reset()
1845 .doRect(shape.path.getMaxX() - shape.insets.right - paintBorder.right, boundsY, paintBorder.right, boundsHeight).getShape());
1849 public boolean isStealthModeEffective() {
1850 return myStealthTabMode && getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition.top;
1854 private boolean isNavigationVisible() {
1855 if (myStealthTabMode && getTabCount() == 1) return false;
1856 return !myVisibleInfos.isEmpty();
1860 public void paint(final Graphics g) {
1861 Rectangle clip = g.getClipBounds();
1862 if (clip == null) {
1863 return;
1866 if (myPaintBlocked) {
1867 if (myImage != null) {
1868 g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null);
1870 return;
1873 super.paint(g);
1876 protected void paintChildren(final Graphics g) {
1877 super.paintChildren(g);
1879 final GraphicsConfig config = new GraphicsConfig(g);
1880 config.setAntialiasing(true);
1881 paintSelectionAndBorder((Graphics2D)g);
1882 config.restore();
1884 final TabLabel selected = getSelectedLabel();
1885 if (selected != null) {
1886 selected.paintImage(g);
1889 mySingleRowLayout.myMoreIcon.paintIcon(this, g);
1892 private Max computeMaxSize() {
1893 Max max = new Max();
1894 for (TabInfo eachInfo : myVisibleInfos) {
1895 final TabLabel label = myInfo2Label.get(eachInfo);
1896 max.myLabel.height = Math.max(max.myLabel.height, label.getPreferredSize().height);
1897 max.myLabel.width = Math.max(max.myLabel.width, label.getPreferredSize().width);
1898 final Toolbar toolbar = myInfo2Toolbar.get(eachInfo);
1899 if (myLayout.isSideComponentOnTabs() && toolbar != null && !toolbar.isEmpty()) {
1900 max.myToolbar.height = Math.max(max.myToolbar.height, toolbar.getPreferredSize().height);
1901 max.myToolbar.width = Math.max(max.myToolbar.width, toolbar.getPreferredSize().width);
1905 max.myToolbar.height++;
1907 return max;
1910 public Dimension getMinimumSize() {
1911 return computeSize(new Transform<JComponent, Dimension>() {
1912 public Dimension transform(JComponent component) {
1913 return component.getMinimumSize();
1915 }, 1);
1918 public Dimension getPreferredSize() {
1919 return computeSize(new Transform<JComponent, Dimension>() {
1920 public Dimension transform(JComponent component) {
1921 return component.getPreferredSize();
1923 }, 3);
1926 private Dimension computeSize(Transform<JComponent, Dimension> transform, int tabCount) {
1927 Dimension size = new Dimension();
1928 for (TabInfo each : myVisibleInfos) {
1929 final JComponent c = each.getComponent();
1930 if (c != null) {
1931 final Dimension eachSize = transform.transform(c);
1932 size.width = Math.max(eachSize.width, size.width);
1933 size.height = Math.max(eachSize.height, size.height);
1937 addHeaderSize(size, tabCount);
1938 return size;
1941 private void addHeaderSize(Dimension size, final int tabsCount) {
1942 Dimension header = computeHeaderPreferredSize(tabsCount);
1944 size.height += header.height;
1945 size.width += header.width;
1947 final Insets insets = getLayoutInsets();
1948 size.width += insets.left + insets.right + 1;
1949 size.height += insets.top + insets.bottom + 1;
1952 private Dimension computeHeaderPreferredSize(int tabsCount) {
1953 final Iterator<TabInfo> infos = myInfo2Label.keySet().iterator();
1954 Dimension size = new Dimension();
1955 int currentTab = 0;
1957 final boolean horizontal = getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
1959 while (infos.hasNext()) {
1960 final boolean canGrow = currentTab < tabsCount;
1962 TabInfo eachInfo = infos.next();
1963 final TabLabel eachLabel = myInfo2Label.get(eachInfo);
1964 final Dimension eachPrefSize = eachLabel.getPreferredSize();
1965 if (horizontal) {
1966 if (canGrow) {
1967 size.width += eachPrefSize.width;
1969 size.height = Math.max(size.height, eachPrefSize.height);
1971 else {
1972 size.width = Math.max(size.width, eachPrefSize.width);
1973 if (canGrow) {
1974 size.height += eachPrefSize.height;
1978 currentTab++;
1981 if (isSingleRow() && isGhostsAlwaysVisible()) {
1982 if (horizontal) {
1983 size.width += getGhostTabLength() * 2;
1985 else {
1986 size.height += getGhostTabLength() * 2;
1990 if (horizontal) {
1991 size.height += myBorder.getTabBorderSize();
1993 else {
1994 size.width += myBorder.getTabBorderSize();
1997 return size;
2000 public int getTabCount() {
2001 return getTabs().size();
2004 @NotNull
2005 public JBTabsPresentation getPresentation() {
2006 return this;
2009 public ActionCallback removeTab(final JComponent component) {
2010 return removeTab(findInfo(component));
2013 public ActionCallback removeTab(final TabInfo info) {
2014 return removeTab(info, null);
2017 public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer) {
2018 return removeTab(info, forcedSelectionTranfer, true);
2021 public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer, boolean transferFocus) {
2022 if (info == null || !getTabs().contains(info)) return new ActionCallback.Done();
2024 final ActionCallback result = new ActionCallback();
2026 TabInfo toSelect;
2027 if (forcedSelectionTranfer == null) {
2028 toSelect = getToSelectOnRemoveOf(info);
2030 else {
2031 assert myVisibleInfos.contains(forcedSelectionTranfer) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer;
2032 toSelect = forcedSelectionTranfer;
2036 if (toSelect != null) {
2037 processRemove(info, false);
2038 _setSelected(toSelect, transferFocus).doWhenProcessed(new Runnable() {
2039 public void run() {
2040 removeDeferred().notifyWhenDone(result);
2044 else {
2045 processRemove(info, true);
2046 removeDeferred().notifyWhenDone(result);
2049 if (myVisibleInfos.isEmpty()) {
2050 removeDeferredNow();
2053 revalidateAndRepaint(true);
2055 return result;
2058 private void processRemove(final TabInfo info, boolean forcedNow) {
2059 remove(myInfo2Label.get(info));
2060 remove(myInfo2Toolbar.get(info));
2062 JComponent tabComponent = info.getComponent();
2064 if (!isToDeferRemoveForLater(tabComponent) || forcedNow) {
2065 remove(tabComponent);
2067 else {
2068 queueForRemove(tabComponent);
2071 myVisibleInfos.remove(info);
2072 myHiddenInfos.remove(info);
2073 myInfo2Label.remove(info);
2074 myInfo2Toolbar.remove(info);
2075 resetTabsCache();
2077 updateAll(false, false);
2080 public TabInfo findInfo(Component component) {
2081 for (TabInfo each : getTabs()) {
2082 if (each.getComponent() == component) return each;
2085 return null;
2088 public TabInfo findInfo(String text) {
2089 if (text == null) return null;
2091 for (TabInfo each : getTabs()) {
2092 if (text.equals(each.getText())) return each;
2095 return null;
2098 public TabInfo findInfo(MouseEvent event) {
2099 return findInfo(event, false);
2102 private TabInfo findInfo(final MouseEvent event, final boolean labelsOnly) {
2103 final Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this);
2104 return _findInfo(point, labelsOnly);
2107 public TabInfo findInfo(final Object object) {
2108 for (int i = 0; i < getTabCount(); i++) {
2109 final TabInfo each = getTabAt(i);
2110 final Object eachObject = each.getObject();
2111 if (eachObject != null && eachObject.equals(object)) return each;
2113 return null;
2116 public TabInfo findTabLabelBy(final Point point) {
2117 return _findInfo(point, true);
2120 private TabInfo _findInfo(final Point point, boolean labelsOnly) {
2121 Component component = findComponentAt(point);
2122 if (component == null) return null;
2123 while (component != this || component != null) {
2124 if (component instanceof TabLabel) {
2125 return ((TabLabel)component).getInfo();
2127 else if (!labelsOnly) {
2128 final TabInfo info = findInfo(component);
2129 if (info != null) return info;
2131 if (component == null) break;
2132 component = component.getParent();
2135 return null;
2138 public void removeAllTabs() {
2139 for (TabInfo each : getTabs()) {
2140 removeTab(each);
2145 private static class Max {
2146 Dimension myLabel = new Dimension();
2147 Dimension myToolbar = new Dimension();
2150 private void updateContainer(boolean forced, final boolean layoutNow) {
2151 final TabLabel selectedLabel = getSelectedLabel();
2153 for (TabInfo each : myVisibleInfos) {
2154 final JComponent eachComponent = each.getComponent();
2155 if (getSelectedInfo() == each && getSelectedInfo() != null) {
2156 unqueueFromRemove(eachComponent);
2158 final Container parent = eachComponent.getParent();
2159 if (parent != null && parent != this) {
2160 parent.remove(eachComponent);
2163 if (eachComponent.getParent() == null) {
2164 add(eachComponent);
2167 else {
2168 if (eachComponent.getParent() == null) continue;
2169 if (isToDeferRemoveForLater(eachComponent)) {
2170 queueForRemove(eachComponent);
2172 else {
2173 remove(eachComponent);
2178 relayout(forced, layoutNow);
2181 protected void addImpl(final Component comp, final Object constraints, final int index) {
2182 unqueueFromRemove(comp);
2184 if (comp instanceof TabLabel) {
2185 ((TabLabel)comp).apply(myUiDecorator.getDecoration());
2188 super.addImpl(comp, constraints, index);
2192 private boolean isToDeferRemoveForLater(JComponent c) {
2193 return c.getRootPane() != null;
2196 void relayout(boolean forced, final boolean layoutNow) {
2197 if (!myForcedRelayout) {
2198 myForcedRelayout = forced;
2200 revalidateAndRepaint(layoutNow);
2203 ActionManager getActionManager() {
2204 return myActionManager;
2207 public TabsBorder getTabsBorder() {
2208 return myBorder;
2211 @NotNull
2212 public JBTabs addTabMouseMotionListener(@NotNull MouseMotionListener listener) {
2213 removeListeners();
2214 myTabMouseListeners.add(listener);
2215 addListeners();
2216 return this;
2219 @NotNull
2220 public JBTabs addTabMouseListener(@NotNull MouseListener listener) {
2221 removeListeners();
2222 myTabMouseListeners.add(listener);
2223 addListeners();
2224 return this;
2227 @NotNull
2228 public JComponent getComponent() {
2229 return this;
2232 @NotNull
2233 public JBTabs removeTabMouseListener(@NotNull MouseListener listener) {
2234 removeListeners();
2235 myTabMouseListeners.remove(listener);
2236 addListeners();
2237 return this;
2240 private void addListeners() {
2241 for (TabInfo eachInfo : myVisibleInfos) {
2242 final TabLabel label = myInfo2Label.get(eachInfo);
2243 for (EventListener eachListener : myTabMouseListeners) {
2244 if (eachListener instanceof MouseListener) {
2245 label.addMouseListener((MouseListener)eachListener);
2247 else if (eachListener instanceof MouseMotionListener) {
2248 label.addMouseMotionListener((MouseMotionListener)eachListener);
2250 else {
2251 assert false;
2257 private void removeListeners() {
2258 for (TabInfo eachInfo : myVisibleInfos) {
2259 final TabLabel label = myInfo2Label.get(eachInfo);
2260 for (EventListener eachListener : myTabMouseListeners) {
2261 if (eachListener instanceof MouseListener) {
2262 label.removeMouseListener((MouseListener)eachListener);
2264 else if (eachListener instanceof MouseMotionListener) {
2265 label.removeMouseMotionListener((MouseMotionListener)eachListener);
2267 else {
2268 assert false;
2274 private void updateListeners() {
2275 removeListeners();
2276 addListeners();
2279 public JBTabs addListener(@NotNull TabsListener listener) {
2280 myTabListeners.add(listener);
2281 return this;
2284 public JBTabs removeListener(@NotNull final TabsListener listener) {
2285 myTabListeners.remove(listener);
2286 return this;
2289 protected void onPopup(final TabInfo popupInfo) {
2292 public void setFocused(final boolean focused) {
2293 if (myFocused == focused) return;
2295 myFocused = focused;
2297 if (myPaintFocus) {
2298 repaint();
2302 public int getIndexOf(@Nullable final TabInfo tabInfo) {
2303 return myVisibleInfos.indexOf(tabInfo);
2306 public boolean isHideTabs() {
2307 return myHideTabs;
2310 public void setHideTabs(final boolean hideTabs) {
2311 if (isHideTabs() == hideTabs) return;
2313 myHideTabs = hideTabs;
2315 relayout(true, false);
2318 public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) {
2319 return myBorder.setPaintBorder(top, left, right, bottom);
2322 public JBTabsPresentation setTabSidePaintBorder(int size) {
2323 return myBorder.setTabSidePaintBorder(size);
2326 static int getBorder(int size) {
2327 return size == -1 ? 1 : size;
2330 public boolean isPaintFocus() {
2331 return myPaintFocus;
2334 @NotNull
2335 public JBTabsPresentation setAdjustBorders(final boolean adjust) {
2336 myAdjustBorders = adjust;
2337 return this;
2340 @NotNull
2341 public JBTabsPresentation setActiveTabFillIn(@Nullable final Color color) {
2342 if (!isChanged(myActiveTabFillIn, color)) return this;
2344 myActiveTabFillIn = color;
2345 revalidateAndRepaint(false);
2346 return this;
2349 private boolean isChanged(Object oldObject, Object newObject) {
2350 if (oldObject == null && newObject == null) return false;
2351 return oldObject != null && !oldObject.equals(newObject) || newObject != null && !newObject.equals(oldObject);
2354 @NotNull
2355 public JBTabsPresentation setTabLabelActionsAutoHide(final boolean autoHide) {
2356 if (myTabLabelActionsAutoHide != autoHide) {
2357 myTabLabelActionsAutoHide = autoHide;
2358 revalidateAndRepaint(false);
2360 return this;
2363 @Nullable
2364 public Color getActiveTabFillIn() {
2365 return myActiveTabFillIn;
2368 public JBTabsPresentation setFocusCycle(final boolean root) {
2369 setFocusCycleRoot(root);
2370 return this;
2374 public JBTabsPresentation setPaintFocus(final boolean paintFocus) {
2375 myPaintFocus = paintFocus;
2376 return this;
2379 private abstract static class BaseNavigationAction extends AnAction {
2381 private final ShadowAction myShadow;
2382 private final ActionManager myActionManager;
2383 private final JBTabsImpl myTabs;
2385 protected BaseNavigationAction(final String copyFromID, JBTabsImpl tabs, ActionManager mgr) {
2386 myActionManager = mgr;
2387 myTabs = tabs;
2388 myShadow = new ShadowAction(this, myActionManager.getAction(copyFromID), tabs);
2389 Disposer.register(tabs, myShadow);
2390 setEnabledInModalContext(true);
2393 public final void update(final AnActionEvent e) {
2394 JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2395 e.getPresentation().setVisible(tabs != null);
2396 if (tabs == null) return;
2398 final int selectedIndex = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2399 final boolean enabled = tabs == myTabs && myTabs.isNavigationVisible() && selectedIndex >= 0 && myTabs.myNavigationActionsEnabled;
2400 e.getPresentation().setEnabled(enabled);
2401 if (enabled) {
2402 _update(e, tabs, selectedIndex);
2406 public void reconnect(String actionId) {
2407 myShadow.reconnect(myActionManager.getAction(actionId));
2410 protected abstract void _update(AnActionEvent e, final JBTabsImpl tabs, int selectedIndex);
2412 public final void actionPerformed(final AnActionEvent e) {
2413 JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2414 if (tabs == null) return;
2416 final int index = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2417 if (index == -1) return;
2418 _actionPerformed(e, tabs, index);
2421 protected abstract void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex);
2424 private static class SelectNextAction extends BaseNavigationAction {
2426 private SelectNextAction(JBTabsImpl tabs, ActionManager mgr) {
2427 super(IdeActions.ACTION_NEXT_TAB, tabs, mgr);
2430 protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2431 e.getPresentation().setEnabled(tabs.findEnabledForward(selectedIndex + 1) != null);
2434 protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2435 tabs.select(tabs.findEnabledForward(selectedIndex + 1), true);
2439 private static class SelectPreviousAction extends BaseNavigationAction {
2440 private SelectPreviousAction(JBTabsImpl tabs, ActionManager mgr) {
2441 super(IdeActions.ACTION_PREVIOUS_TAB, tabs, mgr);
2444 protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2445 e.getPresentation().setEnabled(tabs.findEnabledBackward(selectedIndex - 1) != null);
2448 protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2449 tabs.select(tabs.findEnabledBackward(selectedIndex - 1), true);
2453 private void disposePopupListener() {
2454 if (myActivePopup != null) {
2455 myActivePopup.removePopupMenuListener(myPopupListener);
2456 myActivePopup = null;
2460 public JBTabsPresentation setStealthTabMode(final boolean stealthTabMode) {
2461 myStealthTabMode = stealthTabMode;
2463 relayout(true, false);
2465 return this;
2468 public boolean isStealthTabMode() {
2469 return myStealthTabMode;
2472 public JBTabsPresentation setSideComponentVertical(final boolean vertical) {
2473 myHorizontalSide = !vertical;
2475 for (TabInfo each : myVisibleInfos) {
2476 each.getChangeSupport().firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2");
2480 relayout(true, false);
2482 return this;
2485 public JBTabsPresentation setSingleRow(boolean singleRow) {
2486 myLayout = singleRow ? mySingleRowLayout : myTableLayout;
2488 relayout(true, false);
2490 return this;
2493 public JBTabsPresentation setGhostsAlwaysVisible(final boolean visible) {
2494 myGhostsAlwaysVisible = visible;
2496 relayout(true, false);
2498 return this;
2501 public boolean isGhostsAlwaysVisible() {
2502 return myGhostsAlwaysVisible;
2505 public boolean isSingleRow() {
2506 return getEffectiveLayout() == mySingleRowLayout;
2509 public boolean isSideComponentVertical() {
2510 return !myHorizontalSide;
2513 private TabLayout getEffectiveLayout() {
2514 if (myLayout == myTableLayout && getTabsPosition() == JBTabsPosition.top) return myTableLayout;
2515 return mySingleRowLayout;
2518 public JBTabsPresentation setUiDecorator(UiDecorator decorator) {
2519 myUiDecorator = decorator == null ? ourDefaultDecorator : decorator;
2520 applyDecoration();
2521 return this;
2524 protected void setUI(final ComponentUI newUI) {
2525 super.setUI(newUI);
2526 applyDecoration();
2529 public void updateUI() {
2530 super.updateUI();
2531 SwingUtilities.invokeLater(new Runnable() {
2532 public void run() {
2533 applyDecoration();
2535 revalidateAndRepaint(false);
2540 private void applyDecoration() {
2541 if (myUiDecorator != null) {
2542 UiDecorator.UiDecoration uiDecoration = myUiDecorator.getDecoration();
2543 for (TabLabel each : myInfo2Label.values()) {
2544 each.apply(uiDecoration);
2549 for (TabInfo each : getTabs()) {
2550 adjust(each);
2553 relayout(true, false);
2556 private void adjust(final TabInfo each) {
2557 if (myAdjustBorders) {
2558 UIUtil.removeScrollBorder(each.getComponent());
2562 public void sortTabs(Comparator<TabInfo> comparator) {
2563 Collections.sort(myVisibleInfos, comparator);
2565 relayout(true, false);
2568 public boolean isRequestFocusOnLastFocusedComponent() {
2569 return myRequestFocusOnLastFocusedComponent;
2572 public JBTabsPresentation setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent) {
2573 myRequestFocusOnLastFocusedComponent = requestFocusOnLastFocusedComponent;
2574 return this;
2578 @Nullable
2579 public Object getData(@NonNls final String dataId) {
2580 if (myDataProvider != null) {
2581 final Object value = myDataProvider.getData(dataId);
2582 if (value != null) return value;
2585 return NAVIGATION_ACTIONS_KEY.is(dataId) ? this : null;
2589 public DataProvider getDataProvider() {
2590 return myDataProvider;
2593 public JBTabsImpl setDataProvider(@NotNull final DataProvider dataProvider) {
2594 myDataProvider = dataProvider;
2595 return this;
2599 public boolean isSelectionClick(final MouseEvent e, boolean canBeQuick) {
2600 if (e.getClickCount() == 1 || canBeQuick) {
2601 if (!e.isPopupTrigger()) {
2602 return e.getButton() == MouseEvent.BUTTON1 && !e.isControlDown() && !e.isAltDown() && !e.isMetaDown();
2606 return false;
2610 private static class DefautDecorator implements UiDecorator {
2611 @NotNull
2612 public UiDecoration getDecoration() {
2613 return new UiDecoration(null, new Insets(1, 4, 1, 5));
2617 public static Rectangle layout(JComponent c, Rectangle bounds) {
2618 final Rectangle now = c.getBounds();
2619 if (!bounds.equals(now)) {
2620 c.setBounds(bounds);
2622 c.putClientProperty(LAYOUT_DONE, Boolean.TRUE);
2624 return bounds;
2627 public static Rectangle layout(JComponent c, int x, int y, int width, int height) {
2628 return layout(c, new Rectangle(x, y, width, height));
2631 public static void resetLayout(JComponent c) {
2632 if (c == null) return;
2633 c.putClientProperty(LAYOUT_DONE, null);
2636 private void applyResetComponents() {
2637 for (int i = 0; i < getComponentCount(); i++) {
2638 final Component each = getComponent(i);
2639 if (each instanceof JComponent) {
2640 final JComponent jc = (JComponent)each;
2641 final Object done = jc.getClientProperty(LAYOUT_DONE);
2642 if (!Boolean.TRUE.equals(done)) {
2643 layout(jc, new Rectangle(0, 0, 0, 0));
2650 @NotNull
2651 public JBTabsPresentation setTabLabelActionsMouseDeadzone(final TimedDeadzone.Length length) {
2652 myTabActionsMouseDeadzone = length;
2653 final List<TabInfo> all = getTabs();
2654 for (TabInfo each : all) {
2655 final TabLabel eachLabel = myInfo2Label.get(each);
2656 eachLabel.updateTabActions();
2658 return this;
2661 @NotNull
2662 public JBTabsPresentation setTabsPosition(final JBTabsPosition position) {
2663 myPosition = position;
2664 relayout(true, false);
2665 return this;
2668 public JBTabsPosition getTabsPosition() {
2669 return myPosition;
2672 public TimedDeadzone.Length getTabActionsMouseDeadzone() {
2673 return myTabActionsMouseDeadzone;
2676 public JBTabsPresentation setTabDraggingEnabled(boolean enabled) {
2677 myTabDraggingEnabled = enabled;
2678 return this;
2681 public boolean isTabDraggingEnabled() {
2682 return myTabDraggingEnabled && isSingleRow();
2685 void reallocate(TabInfo source, TabInfo target, boolean before) {
2686 if (source == target || source == null || target == null) return;
2688 final int targetIndex = myVisibleInfos.indexOf(target);
2689 final int sourceIndex = myVisibleInfos.indexOf(source);
2691 boolean needsValidation = false;
2693 myVisibleInfos.remove(source);
2694 myVisibleInfos.add(targetIndex, source);
2695 needsValidation = true;
2697 //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) {
2700 if (needsValidation) {
2701 invalidate();
2702 relayout(true, true);
2706 boolean isHorizontalTabs() {
2707 return getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
2710 public void putInfo(Map<String, String> info) {
2711 final TabInfo selected = getSelectedInfo();
2712 if (selected != null) {
2713 selected.putInfo(info);
2717 public boolean isUseBufferedPaint() {
2718 return myUseBufferedPaint;
2721 public void setUseBufferedPaint(boolean useBufferedPaint) {
2722 myUseBufferedPaint = useBufferedPaint;
2723 revalidate();
2724 repaint();