NPE fixed
[fedora-idea.git] / platform-api / src / com / intellij / ui / tabs / impl / JBTabsImpl.java
blob2d6f4296b18542ea4e3916f59c26b799e01a943c
1 package com.intellij.ui.tabs.impl;
3 import com.intellij.openapi.Disposable;
4 import com.intellij.openapi.actionSystem.*;
5 import com.intellij.openapi.application.ModalityState;
6 import com.intellij.openapi.project.Project;
7 import com.intellij.openapi.ui.ShadowAction;
8 import com.intellij.openapi.util.ActionCallback;
9 import com.intellij.openapi.util.Disposer;
10 import com.intellij.openapi.util.Getter;
11 import com.intellij.openapi.wm.FocusCommand;
12 import com.intellij.openapi.wm.IdeFocusManager;
13 import com.intellij.openapi.wm.impl.content.GraphicsConfig;
14 import com.intellij.ui.CaptionPanel;
15 import com.intellij.ui.tabs.*;
16 import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout;
17 import com.intellij.ui.tabs.impl.table.TableLayout;
18 import com.intellij.ui.tabs.impl.table.TablePassInfo;
19 import com.intellij.util.ui.Animator;
20 import com.intellij.util.ui.UIUtil;
21 import org.jetbrains.annotations.NonNls;
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
25 import javax.swing.*;
26 import javax.swing.event.PopupMenuEvent;
27 import javax.swing.event.PopupMenuListener;
28 import javax.swing.plaf.ComponentUI;
29 import java.awt.*;
30 import java.awt.event.*;
31 import java.awt.geom.GeneralPath;
32 import java.awt.image.BufferedImage;
33 import java.beans.PropertyChangeEvent;
34 import java.beans.PropertyChangeListener;
35 import java.lang.ref.WeakReference;
36 import java.util.*;
37 import java.util.List;
39 public class JBTabsImpl extends JComponent
40 implements JBTabs, PropertyChangeListener, TimerListener, DataProvider, PopupMenuListener, Disposable, JBTabsPresentation {
42 static DataKey<JBTabsImpl> NAVIGATION_ACTIONS_KEY = DataKey.create("JBTabs");
44 ActionManager myActionManager;
45 public final List<TabInfo> myVisibleInfos = new ArrayList<TabInfo>();
46 final Set<TabInfo> myHiddenInfos = new HashSet<TabInfo>();
48 TabInfo mySelectedInfo;
49 public final Map<TabInfo, TabLabel> myInfo2Label = new HashMap<TabInfo, TabLabel>();
50 public final Map<TabInfo, JComponent> myInfo2Toolbar = new HashMap<TabInfo, JComponent>();
51 public Dimension myHeaderFitSize;
53 Insets myInnerInsets = new Insets(0, 0, 0, 0);
55 final List<MouseListener> myTabMouseListeners = new ArrayList<MouseListener>();
56 final List<TabsListener> myTabListeners = new ArrayList<TabsListener>();
57 public boolean myFocused;
59 Getter<ActionGroup> myPopupGroup;
60 String myPopupPlace;
62 TabInfo myPopupInfo;
63 DefaultActionGroup myNavigationActions;
65 PopupMenuListener myPopupListener;
66 JPopupMenu myActivePopup;
68 public boolean myHorizontalSide = true;
70 boolean myStealthTabMode = false;
72 DataProvider myDataProvider;
74 WeakReference<Component> myDeferredToRemove = new WeakReference<Component>(null);
76 SingleRowLayout mySingleRowLayout = new SingleRowLayout(this);
77 TableLayout myTableLayout = new TableLayout(this);
80 private TabLayout myLayout = mySingleRowLayout;
81 private LayoutPassInfo myLastLayoutPass;
83 public boolean myForcedRelayout;
85 private UiDecorator myUiDecorator;
86 static final UiDecorator ourDefaultDecorator = new DefautDecorator();
88 private boolean myPaintFocus = true;
90 private boolean myHideTabs = false;
91 private @Nullable Project myProject;
93 private boolean myRequestFocusOnLastFocusedComponent = false;
94 private boolean myListenerAdded;
95 final Set<TabInfo> myAttractions = new HashSet<TabInfo>();
96 Animator myAnimator;
97 static final String DEFERRED_REMOVE_FLAG = "JBTabs.deferredRemove";
98 List<TabInfo> myAllTabs;
99 boolean myPaintBlocked;
100 BufferedImage myImage;
101 IdeFocusManager myFocusManager;
102 boolean myAdjustBorders = true;
105 private Insets myBorderSize = new Insets(0, 0, 0, 0);
106 boolean myAddNavigationGroup = true;
108 boolean myGhostsAlwaysVisible = false;
109 private boolean myDisposed;
110 private boolean myToDrawBorderIfTabsHidden = true;
111 private Color myActiveTabFillIn;
114 public JBTabsImpl(@Nullable Project project, ActionManager actionManager, IdeFocusManager focusManager, Disposable parent) {
115 myProject = project;
116 myActionManager = actionManager;
117 myFocusManager = focusManager;
119 setOpaque(true);
120 setPaintBorder(-1, -1, -1, -1);
122 Disposer.register(parent, this);
124 myNavigationActions = new DefaultActionGroup();
126 if (myActionManager != null) {
127 myNavigationActions.add(new SelectNextAction(this));
128 myNavigationActions.add(new SelectPreviousAction(this));
131 setUiDecorator(null);
133 UIUtil.addAwtListener(new AWTEventListener() {
134 public void eventDispatched(final AWTEvent event) {
135 if (mySingleRowLayout.myMorePopup != null) return;
136 processFocusChange();
138 }, FocusEvent.FOCUS_EVENT_MASK, this);
140 myPopupListener = new PopupMenuListener() {
141 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
144 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
145 disposePopupListener();
148 public void popupMenuCanceled(final PopupMenuEvent e) {
149 disposePopupListener();
153 addMouseListener(new MouseAdapter() {
154 public void mousePressed(final MouseEvent e) {
155 if (mySingleRowLayout.myLastSingRowLayout != null && mySingleRowLayout.myLastSingRowLayout.moreRect != null && mySingleRowLayout.myLastSingRowLayout.moreRect.contains(e.getPoint())) {
156 showMorePopup(e);
161 Disposer.register(this, new Disposable() {
162 public void dispose() {
163 removeTimerUpdate();
167 myAnimator = new Animator("JBTabs Attractions", 2, 500, true, 0, -1) {
168 public void paintNow(final float frame, final float totalFrames, final float cycle) {
169 repaintAttractions();
172 myAnimator.setTakInitialDelay(false);
174 Disposer.register(this, myAnimator);
176 setFocusCycleRoot(true);
177 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
178 public Component getDefaultComponent(final Container aContainer) {
179 return getToFocus();
183 add(mySingleRowLayout.myLeftGhost);
184 add(mySingleRowLayout.myRightGhost);
187 public void dispose() {
188 myDisposed = true;
189 mySelectedInfo = null;
190 myAllTabs = null;
191 myAttractions.clear();
192 myVisibleInfos.clear();
193 myUiDecorator = null;
194 myImage = null;
195 myActivePopup = null;
196 myInfo2Label.clear();
197 myInfo2Toolbar.clear();
198 myTabListeners.clear();
201 private void processFocusChange() {
202 Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
203 if (owner == null) {
204 setFocused(false);
205 return;
208 if (owner == JBTabsImpl.this || SwingUtilities.isDescendingFrom(owner, JBTabsImpl.this)) {
209 setFocused(true);
211 else {
212 setFocused(false);
216 private void repaintAttractions() {
217 boolean needsUpdate = false;
218 for (TabInfo each : myVisibleInfos) {
219 TabLabel eachLabel = myInfo2Label.get(each);
220 needsUpdate |= eachLabel.repaintAttraction();
223 if (needsUpdate) {
224 relayout(true, false);
228 public void addNotify() {
229 super.addNotify();
231 if (myActionManager != null && !myListenerAdded) {
232 myActionManager.addTimerListener(500, this);
233 myListenerAdded = true;
237 public void removeNotify() {
238 super.removeNotify();
240 setFocused(false);
242 removeTimerUpdate();
245 private void removeTimerUpdate() {
246 if (myActionManager != null && myListenerAdded) {
247 myActionManager.removeTimerListener(this);
248 myListenerAdded = false;
252 public ModalityState getModalityState() {
253 return ModalityState.stateForComponent(this);
256 public void run() {
257 updateTabActions(false);
260 public void updateTabActions(final boolean validateNow) {
261 boolean changed = false;
262 for (TabLabel label : myInfo2Label.values()) {
263 changed |= label.updateTabActions();
266 if (changed) {
267 if (validateNow) {
268 validate();
269 paintImmediately(0, 0, getWidth(), getHeight());
271 else {
272 revalidateAndRepaint(false);
277 private void showMorePopup(final MouseEvent e) {
278 mySingleRowLayout.myMorePopup = new JPopupMenu();
279 for (final TabInfo each : myVisibleInfos) {
280 final JCheckBoxMenuItem item = new JCheckBoxMenuItem(each.getText());
281 mySingleRowLayout.myMorePopup.add(item);
282 if (getSelectedInfo() == each) {
283 item.setSelected(true);
285 item.addActionListener(new ActionListener() {
286 public void actionPerformed(final ActionEvent e) {
287 select(each, true);
292 mySingleRowLayout.myMorePopup.addPopupMenuListener(new PopupMenuListener() {
293 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
296 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
297 mySingleRowLayout.myMorePopup = null;
300 public void popupMenuCanceled(final PopupMenuEvent e) {
301 mySingleRowLayout.myMorePopup = null;
305 mySingleRowLayout.myMorePopup.show(this, e.getX(), e.getY());
309 private JComponent getToFocus() {
310 final TabInfo info = getSelectedInfo();
312 if (info == null) return null;
314 JComponent toFocus = null;
316 if (isRequestFocusOnLastFocusedComponent() && info.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) {
317 toFocus = info.getLastFocusOwner();
320 if (toFocus == null && (info == null || info.getPreferredFocusableComponent() == null)) {
321 return null;
325 if (toFocus == null) {
326 toFocus = info.getPreferredFocusableComponent();
327 final JComponent policyToFocus = myFocusManager != null ? myFocusManager.getFocusTargetFor(toFocus) : null;
328 if (policyToFocus != null) {
329 toFocus = policyToFocus;
333 return toFocus;
336 public void requestFocus() {
337 final JComponent toFocus = getToFocus();
338 if (toFocus != null) {
339 toFocus.requestFocus();
341 else {
342 super.requestFocus();
346 public boolean requestFocusInWindow() {
347 final JComponent toFocus = getToFocus();
348 if (toFocus != null) {
349 return toFocus.requestFocusInWindow();
351 else {
352 return super.requestFocusInWindow();
356 private JBTabsImpl findTabs(Component c) {
357 Component eachParent = c;
358 while (eachParent != null) {
359 if (eachParent instanceof JBTabsImpl) {
360 return (JBTabsImpl)eachParent;
362 eachParent = eachParent.getParent();
365 return null;
369 @NotNull
370 public TabInfo addTab(TabInfo info, int index) {
371 if (getTabs().contains(info)) {
372 return getTabs().get(getTabs().indexOf(info));
375 info.getChangeSupport().addPropertyChangeListener(this);
376 final TabLabel label = new TabLabel(this, info);
377 myInfo2Label.put(info, label);
379 if (index < 0) {
380 myVisibleInfos.add(info);
382 else if (index > myVisibleInfos.size() - 1) {
383 myVisibleInfos.add(info);
385 else {
386 myVisibleInfos.add(index, info);
389 myAllTabs = null;
391 add(label);
393 updateText(info);
394 updateIcon(info);
395 updateSideComponent(info);
396 updateTabActions(info);
398 updateAll(false, true);
400 if (info.isHidden()) {
401 updateHiding();
405 adjust(info);
407 revalidateAndRepaint(false);
409 return info;
413 @NotNull
414 public TabInfo addTab(TabInfo info) {
415 return addTab(info, -1);
418 public ActionGroup getPopupGroup() {
419 return myPopupGroup != null ? myPopupGroup.get() : null;
422 public String getPopupPlace() {
423 return myPopupPlace;
426 public JBTabs setPopupGroup(@NotNull final ActionGroup popupGroup, @NotNull String place, final boolean addNavigationGroup) {
427 return setPopupGroup(new Getter<ActionGroup>() {
428 public ActionGroup get() {
429 return popupGroup;
431 }, place, addNavigationGroup);
434 public JBTabs setPopupGroup(@NotNull final Getter<ActionGroup> popupGroup,
435 @NotNull final String place,
436 final boolean addNavigationGroup) {
437 myPopupGroup = popupGroup;
438 myPopupPlace = place;
439 myAddNavigationGroup = addNavigationGroup;
440 return this;
443 private void updateAll(final boolean forcedRelayout, final boolean now) {
444 mySelectedInfo = getSelectedInfo();
445 removeDeferred(updateContainer(forcedRelayout, now));
446 updateListeners();
447 updateTabActions(false);
450 private boolean isMyChildIsFocusedNow() {
451 final Component owner = getFocusOwner();
452 if (owner == null) return false;
455 if (mySelectedInfo != null) {
456 if (!SwingUtilities.isDescendingFrom(owner, mySelectedInfo.getComponent())) return false;
459 return SwingUtilities.isDescendingFrom(owner, this);
462 @Nullable
463 private JComponent getFocusOwner() {
464 final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
465 return (JComponent)(owner instanceof JComponent ? owner : null);
468 public ActionCallback select(@NotNull TabInfo info, boolean requestFocus) {
469 return _setSelected(info, requestFocus);
472 private ActionCallback _setSelected(final TabInfo info, final boolean requestFocus) {
473 if (mySelectedInfo != null && mySelectedInfo.equals(info)) {
474 if (!requestFocus) {
475 return new ActionCallback.Done();
477 else {
478 requestFocus(getToFocus());
483 if (myRequestFocusOnLastFocusedComponent && mySelectedInfo != null) {
484 if (isMyChildIsFocusedNow()) {
485 mySelectedInfo.setLastFocusOwner(getFocusOwner());
489 TabInfo oldInfo = mySelectedInfo;
490 mySelectedInfo = info;
491 final TabInfo newInfo = getSelectedInfo();
493 final Component deferredRemove = updateContainer(false, true);
495 if (oldInfo != newInfo) {
496 for (TabsListener eachListener : myTabListeners) {
497 if (eachListener != null) {
498 eachListener.selectionChanged(oldInfo, newInfo);
503 if (requestFocus) {
504 final JComponent toFocus = getToFocus();
505 if (myProject != null && toFocus != null) {
506 final ActionCallback result = new ActionCallback();
507 requestFocus(toFocus).doWhenProcessed(new Runnable() {
508 public void run() {
509 if (myDisposed) {
510 result.setRejected();
511 } else {
512 removeDeferred(deferredRemove).notifyWhenDone(result);
516 return result;
518 else {
519 requestFocus();
520 return removeDeferred(deferredRemove);
523 else {
524 return removeDeferred(deferredRemove);
528 private ActionCallback requestFocus(final JComponent toFocus) {
529 if (toFocus == null) return new ActionCallback.Done();
531 return myFocusManager.requestFocus(new FocusCommand(toFocus) {
532 public ActionCallback run() {
533 toFocus.requestFocus();
534 return new ActionCallback.Done();
536 }, true);
539 private ActionCallback removeDeferred(final Component deferredRemove) {
540 final ActionCallback callback = new ActionCallback();
541 if (deferredRemove != null) {
542 SwingUtilities.invokeLater(new Runnable() {
543 public void run() {
544 if (isForDeferredRemove(deferredRemove)) {
545 remove(deferredRemove);
547 callback.setDone();
551 else {
552 callback.setDone();
555 return callback;
558 private boolean isForDeferredRemove(Component c) {
559 if (c instanceof JComponent) {
560 if (((JComponent)c).getClientProperty(DEFERRED_REMOVE_FLAG) == null) return false;
562 if (mySelectedInfo != null && mySelectedInfo.getComponent() == c) {
563 return false;
565 else {
566 return true;
571 return false;
574 private void setForDeferredRemove(Component c, boolean toRemove) {
575 if (c instanceof JComponent) {
576 ((JComponent)c).putClientProperty(DEFERRED_REMOVE_FLAG, toRemove ? Boolean.TRUE : null);
577 c.setBounds(0, 0, 0, 0);
578 if (toRemove) {
579 removeCurrentDeferred();
580 setDeferredToRemove(c);
582 else if (getDeferredToRemove() != null && getDeferredToRemove() == c) {
583 setDeferredToRemove(null);
588 private void removeCurrentDeferred() {
589 if (getDeferredToRemove() != null) {
590 remove(getDeferredToRemove());
591 setDeferredToRemove(null);
595 @Nullable
596 public void propertyChange(final PropertyChangeEvent evt) {
597 final TabInfo tabInfo = (TabInfo)evt.getSource();
598 if (TabInfo.ACTION_GROUP.equals(evt.getPropertyName())) {
599 updateSideComponent(tabInfo);
601 else if (TabInfo.TEXT.equals(evt.getPropertyName())) {
602 updateText(tabInfo);
604 else if (TabInfo.ICON.equals(evt.getPropertyName())) {
605 updateIcon(tabInfo);
607 else if (TabInfo.ALERT_STATUS.equals(evt.getPropertyName())) {
608 boolean start = ((Boolean)evt.getNewValue()).booleanValue();
609 updateAttraction(tabInfo, start);
611 else if (TabInfo.TAB_ACTION_GROUP.equals(evt.getPropertyName())) {
612 updateTabActions(tabInfo);
614 else if (TabInfo.HIDDEN.equals(evt.getPropertyName())) {
615 updateHiding();
618 relayout(false, false);
621 private void updateHiding() {
622 boolean update = false;
624 Iterator<TabInfo> visible = myVisibleInfos.iterator();
625 while (visible.hasNext()) {
626 TabInfo each = visible.next();
627 if (each.isHidden() && !myHiddenInfos.contains(each)) {
628 myHiddenInfos.add(each);
629 visible.remove();
630 update = true;
635 Iterator<TabInfo> hidden = myHiddenInfos.iterator();
636 while (hidden.hasNext()) {
637 TabInfo each = hidden.next();
638 if (!each.isHidden() && myHiddenInfos.contains(each)) {
639 myVisibleInfos.add(each);
640 hidden.remove();
641 update = true;
646 if (update) {
647 myAllTabs = null;
648 if (mySelectedInfo != null && myHiddenInfos.contains(mySelectedInfo)) {
649 mySelectedInfo = getToSelectOnRemoveOf(mySelectedInfo);
651 updateAll(true, false);
655 private void updateIcon(final TabInfo tabInfo) {
656 myInfo2Label.get(tabInfo).setIcon(tabInfo.getIcon());
657 revalidateAndRepaint(false);
660 void revalidateAndRepaint(final boolean layoutNow) {
661 if (myVisibleInfos.size() == 0) {
662 setOpaque(false);
663 final Component nonOpaque = UIUtil.findUltimateParent(this);
664 if (nonOpaque != null && getParent() != null) {
665 final Rectangle toRepaint = SwingUtilities.convertRectangle(getParent(), getBounds(), nonOpaque);
666 nonOpaque.repaint(toRepaint.x, toRepaint.y, toRepaint.width, toRepaint.height);
669 else {
670 setOpaque(true);
673 if (layoutNow) {
674 validate();
676 else {
677 revalidate();
680 repaint();
684 private void updateAttraction(final TabInfo tabInfo, boolean start) {
685 if (start) {
686 myAttractions.add(tabInfo);
688 else {
689 myAttractions.remove(tabInfo);
690 tabInfo.setBlinkCount(0);
693 if (start && !myAnimator.isRunning()) {
694 myAnimator.resume();
696 else if (!start && myAttractions.size() == 0) {
697 myAnimator.suspend();
698 repaintAttractions();
702 private void updateText(final TabInfo tabInfo) {
703 final TabLabel label = myInfo2Label.get(tabInfo);
704 label.setText(tabInfo.getColoredText());
705 label.setToolTipText(tabInfo.getTooltipText());
706 revalidateAndRepaint(false);
709 private void updateSideComponent(final TabInfo tabInfo) {
710 final JComponent old = myInfo2Toolbar.get(tabInfo);
711 if (old != null) {
712 remove(old);
714 final JComponent toolbar = createToolbarComponent(tabInfo);
715 if (toolbar != null) {
716 myInfo2Toolbar.put(tabInfo, toolbar);
717 add(toolbar);
721 private void updateTabActions(final TabInfo info) {
722 myInfo2Label.get(info).setTabActions(info.getTabLabelActions());
725 @Nullable
726 public TabInfo getSelectedInfo() {
727 if (!myVisibleInfos.contains(mySelectedInfo)) {
728 mySelectedInfo = null;
730 return mySelectedInfo != null ? mySelectedInfo : (myVisibleInfos.size() > 0 ? myVisibleInfos.get(0) : null);
733 @Nullable
734 private TabInfo getToSelectOnRemoveOf(TabInfo info) {
735 if (!myVisibleInfos.contains(info)) return null;
736 if (mySelectedInfo != info) return null;
738 if (myVisibleInfos.size() == 1) return null;
740 int index = myVisibleInfos.indexOf(info);
741 if (index > 0) return myVisibleInfos.get(index - 1);
742 if (index < myVisibleInfos.size() - 1) return myVisibleInfos.get(index + 1);
744 return null;
747 protected JComponent createToolbarComponent(final TabInfo tabInfo) {
748 return new Toolbar(this, tabInfo);
751 @NotNull
752 public TabInfo getTabAt(final int tabIndex) {
753 return getTabs().get(tabIndex);
756 @NotNull
757 public List<TabInfo> getTabs() {
758 if (myAllTabs != null) return myAllTabs;
760 ArrayList<TabInfo> result = new ArrayList<TabInfo>();
761 result.addAll(myVisibleInfos);
762 result.addAll(myHiddenInfos);
764 myAllTabs = result;
766 return result;
769 public TabInfo getTargetInfo() {
770 return myPopupInfo != null ? myPopupInfo : getSelectedInfo();
773 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
776 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
777 resetPopup();
780 public void popupMenuCanceled(final PopupMenuEvent e) {
781 resetPopup();
784 private void resetPopup() {
785 //todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled
786 SwingUtilities.invokeLater(new Runnable() {
787 public void run() {
788 myPopupInfo = null;
793 public void setPaintBlocked(boolean blocked) {
794 if (blocked && !myPaintBlocked) {
795 myImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
796 final Graphics2D g = myImage.createGraphics();
797 super.paint(g);
798 g.dispose();
801 myPaintBlocked = blocked;
803 if (!myPaintBlocked) {
804 if (myImage != null) {
805 myImage.flush();
808 myImage = null;
809 repaint();
813 @Nullable
814 private Component getDeferredToRemove() {
815 return myDeferredToRemove != null ? myDeferredToRemove.get() : null;
818 private void setDeferredToRemove(final Component c) {
819 myDeferredToRemove = new WeakReference<Component>(c);
822 public boolean isToDrawBorderIfTabsHidden() {
823 return myToDrawBorderIfTabsHidden;
826 @NotNull
827 public JBTabsPresentation setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden) {
828 myToDrawBorderIfTabsHidden = toDrawBorderIfTabsHidden;
829 return this;
832 @NotNull
833 public JBTabs getJBTabs() {
834 return this;
837 public static class Toolbar extends JPanel {
838 private JBTabsImpl myTabs;
840 public Toolbar(JBTabsImpl tabs, TabInfo info) {
841 myTabs = tabs;
843 setLayout(new BorderLayout());
845 final ActionGroup group = info.getGroup();
846 final JComponent side = info.getSideComponent();
848 if (group != null && myTabs.myActionManager != null) {
849 final String place = info.getPlace();
850 ActionToolbar toolbar = myTabs.myActionManager.createActionToolbar(place != null ? place : ActionPlaces.UNKNOWN, group, myTabs.myHorizontalSide);
851 toolbar.setTargetComponent(info.getActionsContextComponent());
852 final JComponent actionToolbar = toolbar.getComponent();
853 add(actionToolbar, BorderLayout.CENTER);
856 if (side != null) {
857 if (group != null) {
858 add(side, BorderLayout.EAST);
860 else {
861 add(side, BorderLayout.CENTER);
870 public void doLayout() {
871 try {
872 final Max max = computeMaxSize();
873 myHeaderFitSize =
874 new Dimension(getSize().width, myHorizontalSide ? Math.max(max.myLabel.height, max.myToolbar.height) : max.myLabel.height);
876 if (isSingleRow()) {
877 myLastLayoutPass = mySingleRowLayout.layoutSingleRow();
878 myTableLayout.myLastTableLayout = null;
880 else {
881 myLastLayoutPass = myTableLayout.layoutTable();
882 mySingleRowLayout.myLastSingRowLayout = null;
885 if (isStealthModeEffective()) {
886 final TabLabel label = myInfo2Label.get(getSelectedInfo());
887 final Rectangle bounds = label.getBounds();
888 final Insets insets = getLayoutInsets();
889 label.setBounds(bounds.x, bounds.y, getWidth() - insets.right - insets.left, bounds.height);
893 finally {
894 myForcedRelayout = false;
899 public void layoutComp(int xAddin, int yComp, final JComponent comp) {
900 final Insets insets = getLayoutInsets();
902 final Insets border =
903 isHideTabs() ? new Insets(0, 0, 0, 0) : (Insets)myBorderSize.clone();
904 if (isStealthModeEffective() || isHideTabs()) {
905 border.top = getBorder(-1);
906 border.bottom = getBorder(-1);
907 border.left = getBorder(-1);
908 border.right = getBorder(-1);
911 final Insets inner = getInnerInsets();
912 border.top += inner.top;
913 border.bottom += inner.bottom;
914 border.left += inner.left;
915 border.right += inner.right;
917 comp.setBounds(insets.left + xAddin + border.left, yComp + border.top,
918 getWidth() - insets.left - insets.right - xAddin - border.left - border.right,
919 getHeight() - insets.bottom - yComp - border.top - border.bottom);
923 public JBTabsPresentation setInnerInsets(final Insets innerInsets) {
924 myInnerInsets = innerInsets;
925 return this;
928 public Insets getInnerInsets() {
929 return myInnerInsets;
932 public Insets getLayoutInsets() {
933 Insets insets = getInsets();
934 if (insets == null) {
935 insets = new Insets(0, 0, 0, 0);
937 return insets;
940 private int fixInset(int inset, int addin) {
941 return inset + addin;
946 public int getToolbarInset() {
947 return getArcSize() + 1;
950 public void resetLayout(boolean resetLabels) {
951 if (resetLabels) {
952 mySingleRowLayout.myLeftGhost.reset();
953 mySingleRowLayout.myRightGhost.reset();
956 for (TabInfo each : myVisibleInfos) {
957 reset(each, resetLabels);
960 for (TabInfo each : myHiddenInfos) {
961 reset(each, resetLabels);
965 private void reset(final TabInfo each, final boolean resetLabels) {
966 final JComponent c = each.getComponent();
967 if (c != null) {
968 c.setBounds(0, 0, 0, 0);
971 final JComponent toolbar = myInfo2Toolbar.get(each);
972 if (toolbar != null) {
973 toolbar.setBounds(0, 0, 0, 0);
976 if (resetLabels) {
977 myInfo2Label.get(each).setBounds(0, 0, 0, 0);
982 private int getArcSize() {
983 return 4;
986 public int getGhostTabWidth() {
987 return 15;
991 protected void paintComponent(final Graphics g) {
992 super.paintComponent(g);
994 if (myVisibleInfos.size() == 0) return;
996 final GraphicsConfig config = new GraphicsConfig(g);
997 config.setAntialiasing(true);
999 Graphics2D g2d = (Graphics2D)g;
1002 g.setColor(getBackground());
1003 g.fillRect(0, 0, getWidth(), getHeight());
1005 int arc = getArcSize();
1007 final Color topBlickColor = getTopBlickColor();
1008 final Color rightBlockColor = getRightBlockColor();
1009 final Color boundsColor = getBoundsColor();
1011 Insets insets = getLayoutInsets();
1013 final TabInfo selected = getSelectedInfo();
1015 final int selectionTabVShift = getSelectionTabVShift();
1016 int curveArc = 2;
1018 boolean leftGhostExists = isSingleRow();
1019 boolean rightGhostExists = isSingleRow();
1021 if (!isStealthModeEffective() && !isHideTabs()) {
1022 if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.rightGhostVisible) {
1023 int topX = mySingleRowLayout.myLastSingRowLayout.rightGhost.x - arc;
1024 int topY = mySingleRowLayout.myLastSingRowLayout.rightGhost.y + selectionTabVShift;
1025 int bottomX = (int)(mySingleRowLayout.myLastSingRowLayout.rightGhost.getMaxX() - curveArc);
1026 int bottomY = (int)mySingleRowLayout.myLastSingRowLayout.rightGhost.getMaxY() + 1;
1028 final GeneralPath path = new GeneralPath();
1029 path.moveTo(topX, topY);
1030 path.lineTo(bottomX, topY);
1031 path.quadTo(bottomX - curveArc, topY + (bottomY - topY) / 4, bottomX, topY + (bottomY - topY) / 2);
1032 path.quadTo(bottomX + curveArc, bottomY - (bottomY - topY) / 4, bottomX, bottomY);
1033 path.lineTo(topX, bottomY);
1035 path.closePath();
1037 g2d.setColor(getBackground());
1038 g2d.fill(path);
1040 g2d.setColor(boundsColor);
1041 g2d.draw(path);
1043 g2d.setColor(topBlickColor);
1044 g2d.drawLine(topX, topY + 1, bottomX - curveArc, topY + 1);
1048 paintNonSelectedTabs(g2d, leftGhostExists);
1050 if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.leftGhostVisible) {
1051 final GeneralPath path = new GeneralPath();
1053 int topX = mySingleRowLayout.myLastSingRowLayout.leftGhost.x + curveArc;
1054 int topY = mySingleRowLayout.myLastSingRowLayout.leftGhost.y + selectionTabVShift;
1055 int bottomX = (int)mySingleRowLayout.myLastSingRowLayout.leftGhost.getMaxX() + 1;
1056 int bottomY = (int)(mySingleRowLayout.myLastSingRowLayout.leftGhost.getMaxY() + 1);
1058 path.moveTo(topX, topY);
1060 final boolean isLeftFromSelection = mySingleRowLayout.myLastSingRowLayout.toLayout.indexOf(getSelectedInfo()) == 0;
1062 if (isLeftFromSelection) {
1063 path.lineTo(bottomX, topY);
1065 else {
1066 path.lineTo(bottomX - arc, topY);
1067 path.quadTo(bottomX, topY, bottomX, topY + arc);
1070 path.lineTo(bottomX, bottomY);
1071 path.lineTo(topX, bottomY);
1073 path.quadTo(topX - curveArc * 2 + 1, bottomY - (bottomY - topY) / 4, topX, (bottomY - topY) / 2);
1075 path.quadTo(topX + curveArc - 1, topY + (bottomY - topY) / 4, topX, topY);
1077 path.closePath();
1079 g2d.setColor(getBackground());
1080 g2d.fill(path);
1082 g.setColor(boundsColor);
1083 g2d.draw(path);
1085 g.setColor(topBlickColor);
1086 g.drawLine(topX + 1, topY + 1, bottomX - arc, topY + 1);
1088 g.setColor(rightBlockColor);
1089 g2d.drawLine(bottomX - 1, topY + arc, bottomX - 1, bottomY - 1);
1094 if (selected == null) return;
1097 final TabLabel selectedLabel = myInfo2Label.get(selected);
1098 if (selectedLabel == null) return;
1100 Rectangle selectedTabBounds = selectedLabel.getBounds();
1103 final GeneralPath path = new GeneralPath();
1104 int bottomY = (int)selectedTabBounds.getMaxY() + 1;
1105 final int topY = selectedTabBounds.y;
1106 int leftX = selectedTabBounds.x;
1108 int rightX = selectedTabBounds.x + selectedTabBounds.width;
1110 path.moveTo(insets.left, bottomY);
1111 path.lineTo(leftX, bottomY);
1112 path.lineTo(leftX, topY + arc);
1113 path.quadTo(leftX, topY, leftX + arc, topY);
1115 int lastX = getWidth() - insets.right - 1;
1117 if (isStealthModeEffective()) {
1118 path.lineTo(lastX - arc, topY);
1119 path.quadTo(lastX, topY, lastX, topY + arc);
1120 path.lineTo(lastX, bottomY);
1122 else {
1123 path.lineTo(rightX - arc, topY);
1124 path.quadTo(rightX, topY, rightX, topY + arc);
1125 if (myLastLayoutPass.hasCurveSpaceFor(selected)) {
1126 path.lineTo(rightX, bottomY - arc);
1127 path.quadTo(rightX, bottomY, rightX + arc, bottomY);
1128 } else {
1129 path.lineTo(rightX, bottomY);
1133 path.lineTo(lastX, bottomY);
1135 if (isStealthModeEffective()) {
1136 path.closePath();
1139 final GeneralPath fillPath = (GeneralPath)path.clone();
1140 if (!isHideTabs()) {
1141 fillPath.lineTo(lastX, bottomY + 1);
1142 fillPath.lineTo(leftX, bottomY + 1);
1143 fillPath.closePath();
1144 g2d.setColor(getBackground());
1145 g2d.fill(fillPath);
1149 final Color from;
1150 final Color to;
1151 final int alpha;
1152 int paintTopY = topY;
1153 int paintBottomY = bottomY;
1154 final boolean paintFocused = myPaintFocus && (myFocused || myActivePopup != null);
1155 Color bgPreFill = null;
1156 if (paintFocused) {
1157 if (getActiveTabFillIn() == null) {
1158 from = UIUtil.getFocusedFillColor();
1159 to = UIUtil.getFocusedFillColor();
1160 } else {
1161 bgPreFill = getActiveTabFillIn();
1162 alpha = 255;
1163 paintBottomY = topY + getArcSize() - 2;
1164 from = UIUtil.toAlpha(UIUtil.getFocusedFillColor(), alpha);
1165 to = UIUtil.toAlpha(getActiveTabFillIn(), alpha);
1168 else {
1169 if (isPaintFocus()) {
1170 if (getActiveTabFillIn() == null) {
1171 alpha = 150;
1172 from = UIUtil.toAlpha(UIUtil.getPanelBackgound().brighter(), alpha);
1173 to = UIUtil.toAlpha(UIUtil.getPanelBackgound(), alpha);
1174 } else {
1175 alpha = 255;
1176 from = UIUtil.toAlpha(getActiveTabFillIn(), alpha);
1177 to = UIUtil.toAlpha(getActiveTabFillIn(), alpha);
1180 else {
1181 alpha = 255;
1182 from = UIUtil.toAlpha(Color.white, alpha);
1183 to = UIUtil.toAlpha(Color.white, alpha);
1187 if (!isHideTabs()) {
1188 if (bgPreFill != null) {
1189 g2d.setColor(bgPreFill);
1190 g2d.fill(fillPath);
1192 g2d.setPaint(new GradientPaint(selectedTabBounds.x, paintTopY, from, selectedTabBounds.x, paintBottomY, to));
1193 g2d.fill(fillPath);
1196 Color borderColor = UIUtil.getBoundsColor(paintFocused);
1197 g2d.setColor(borderColor);
1199 if (!isHideTabs()) {
1200 g2d.draw(path);
1203 if (isHideTabs()) {
1204 paintBorder(g2d, insets.left, insets.top, getWidth() - insets.left - insets.right, getHeight() - insets.bottom - insets.top,
1205 borderColor, from, to, paintFocused);
1207 else {
1208 paintBorder(g2d, insets.left, bottomY, getWidth() - insets.left - insets.right, getHeight() - bottomY - insets.bottom, borderColor,
1209 from, to, paintFocused);
1212 config.setAntialiasing(false);
1213 if (isSideComponentVertical()) {
1214 JComponent toolbarComp = myInfo2Toolbar.get(mySelectedInfo);
1215 if (toolbarComp != null) {
1216 Rectangle toolBounds = toolbarComp.getBounds();
1217 g2d.setColor(CaptionPanel.CNT_ACTIVE_COLOR);
1218 g.drawLine((int)toolBounds.getMaxX(), toolBounds.y, (int)toolBounds.getMaxX(), (int)toolBounds.getMaxY() - 1);
1222 config.restore();
1225 private Color getBoundsColor() {
1226 return Color.gray;
1229 private Color getRightBlockColor() {
1230 return Color.lightGray;
1233 private Color getTopBlickColor() {
1234 return Color.white;
1237 private void paintNonSelectedTabs(final Graphics2D g2d, final boolean leftGhostExists) {
1238 for (int eachRow = 0; eachRow < myLastLayoutPass.getRowCount(); eachRow++) {
1239 for (int eachColumn = myLastLayoutPass.getColumnCount(eachRow) - 1; eachColumn >= 0; eachColumn--) {
1240 final TabInfo each = myLastLayoutPass.getTabAt(eachRow, eachColumn);
1241 if (getSelectedInfo() == each) continue;
1242 paintTab(g2d, each, leftGhostExists);
1247 private void paintTab(final Graphics2D g2d, final TabInfo each, final boolean leftGhostExists) {
1248 int tabIndex = myVisibleInfos.indexOf(each);
1250 final int arc = getArcSize();
1251 final Color topBlickColor = getTopBlickColor();
1252 final Color rightBlockColor = getRightBlockColor();
1253 final Color boundsColor = getBoundsColor();
1254 final TabInfo selected = getSelectedInfo();
1255 final int selectionTabVShift = getSelectionTabVShift();
1258 final TabLabel eachLabel = myInfo2Label.get(each);
1259 if (eachLabel.getBounds().width == 0) return;
1262 final TabInfo prev = myLastLayoutPass.getPreviousFor(myVisibleInfos.get(tabIndex));
1263 final TabInfo next = myLastLayoutPass.getNextFor(myVisibleInfos.get(tabIndex));
1265 final Rectangle eachBounds = eachLabel.getBounds();
1266 final GeneralPath path = new GeneralPath();
1268 boolean firstShowing = prev == null;
1269 if (!firstShowing && !leftGhostExists) {
1270 firstShowing = myInfo2Label.get(prev).getBounds().width == 0;
1273 boolean lastShowing = next == null;
1274 if (!lastShowing) {
1275 lastShowing = myInfo2Label.get(next).getBounds().width == 0;
1278 boolean leftFromSelection = selected != null && tabIndex == myVisibleInfos.indexOf(selected) - 1;
1281 int leftX = firstShowing ? eachBounds.x : eachBounds.x - arc - 1;
1282 int topY = eachBounds.y + selectionTabVShift;
1283 int rigthX = !lastShowing && leftFromSelection ? (int)eachBounds.getMaxX() + arc + 1 : (int)eachBounds.getMaxX();
1284 int bottomY = (int)eachBounds.getMaxY() + 1;
1286 path.moveTo(leftX, bottomY);
1287 path.lineTo(leftX, topY + arc);
1288 path.quadTo(leftX, topY, leftX + arc, topY);
1289 path.lineTo(rigthX - arc, topY);
1290 path.quadTo(rigthX, topY, rigthX, topY + arc);
1291 path.lineTo(rigthX, bottomY);
1293 if (!isSingleRow()) {
1294 final TablePassInfo info = myTableLayout.myLastTableLayout;
1295 if (!info.isInSelectionRow(each)) {
1296 path.lineTo(rigthX, bottomY + getArcSize());
1297 path.lineTo(leftX, bottomY + getArcSize());
1298 path.lineTo(leftX, bottomY);
1302 path.closePath();
1304 g2d.setColor(getBackground());
1305 g2d.fill(path);
1307 g2d.setColor(topBlickColor);
1308 g2d.drawLine(leftX + arc, topY + 1, rigthX - arc, topY + 1);
1310 g2d.setColor(rightBlockColor);
1311 g2d.drawLine(rigthX - 1, topY + arc - 1, rigthX - 1, bottomY);
1313 g2d.setColor(boundsColor);
1314 g2d.draw(path);
1317 public int getSelectionTabVShift() {
1318 return 2;
1321 private void paintBorder(Graphics2D g2d,
1322 int x,
1323 int y,
1324 int width,
1325 int height,
1326 final Color borderColor,
1327 final Color fillFrom,
1328 final Color fillTo,
1329 boolean isFocused) {
1330 int topY = y + 1;
1331 int bottomY = y + myBorderSize.top - 2;
1332 int middleY = topY + (bottomY - topY) / 2;
1334 if (myBorderSize.top > 0) {
1335 if (isHideTabs()) {
1336 if (isToDrawBorderIfTabsHidden()) {
1337 g2d.setColor(borderColor);
1338 g2d.drawLine(x, y, x + width - 1, y);
1341 else if (isStealthModeEffective()) {
1342 g2d.setColor(borderColor);
1343 g2d.drawLine(x, y - 1, x + width - 1, y - 1);
1345 else if (getActiveTabFillIn() == null) {
1346 if (myBorderSize.top > 1) {
1347 g2d.setColor(Color.white);
1348 g2d.fillRect(x, topY, width, bottomY - topY);
1350 g2d.setColor(fillTo);
1351 g2d.fillRect(x, topY, width, middleY - topY);
1353 final Color relfectionStartColor =
1354 isFocused ? UIUtil.toAlpha(UIUtil.getListSelectionBackground().darker(), 125) : UIUtil.toAlpha(borderColor, 75);
1355 g2d.setPaint(new GradientPaint(x, middleY, relfectionStartColor, x, bottomY, UIUtil.toAlpha(Color.white, 255)));
1356 g2d.fillRect(x, middleY, width, bottomY - middleY);
1358 g2d.setColor(UIUtil.toAlpha(Color.white, 100));
1359 g2d.drawLine(x, topY, x + width - 1, topY);
1362 g2d.setColor(Color.lightGray);
1363 g2d.drawLine(x, bottomY, x + width - 1, bottomY);
1365 else if (myBorderSize.top == 1) {
1366 g2d.setColor(borderColor);
1367 g2d.drawLine(x, y, x + width - 1, y);
1372 g2d.setColor(borderColor);
1373 g2d.fillRect(x, y + height - myBorderSize.bottom, width, myBorderSize.bottom);
1375 g2d.fillRect(x, y, myBorderSize.left, height);
1376 g2d.fillRect(x + width - myBorderSize.right, y, myBorderSize.right, height);
1379 public boolean isStealthModeEffective() {
1380 return myStealthTabMode && getTabCount() == 1 && isSideComponentVertical();
1384 private boolean isNavigationVisible() {
1385 if (myStealthTabMode && getTabCount() == 1) return false;
1386 return myVisibleInfos.size() > 0;
1390 public void paint(final Graphics g) {
1391 if (myPaintBlocked) {
1392 g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null);
1393 return;
1396 super.paint(g);
1399 protected void paintChildren(final Graphics g) {
1400 super.paintChildren(g);
1402 //if (isSingleRow() && myLastSingRowLayout != null) {
1403 // final List<TabInfo> infos = myLastSingRowLayout.toLayout;
1404 // for (int i = 1; i < infos.size(); i++) {
1405 // final TabInfo each = infos.get(i);
1406 // if (getSelectedInfo() != each && getSelectedInfo() != infos.get(i - 1)) {
1407 // drawSeparator(g, each);
1408 // }
1409 // }
1411 //else if (!isSingleRow() && myLastTableLayout != null) {
1412 // final List<TableRow> table = myLastTableLayout.table;
1413 // for (TableRow eachRow : table) {
1414 // final List<TabInfo> infos = eachRow.myColumns;
1415 // for (int i = 1; i < infos.size(); i++) {
1416 // final TabInfo each = infos.get(i);
1417 // if (getSelectedInfo() != each && getSelectedInfo() != infos.get(i - 1)) {
1418 // drawSeparator(g, each);
1419 // }
1420 // }
1421 // }
1424 mySingleRowLayout.myMoreIcon.paintIcon(this, g);
1427 private void drawSeparator(Graphics g, TabInfo info) {
1428 final TabLabel label = myInfo2Label.get(info);
1429 if (label == null) return;
1430 final Rectangle bounds = label.getBounds();
1432 final double height = bounds.height * 0.85d;
1433 final double delta = bounds.height - height;
1435 final int y1 = (int)(bounds.y + delta) + 1;
1436 final int x1 = bounds.x;
1437 final int y2 = (int)(bounds.y + bounds.height - delta);
1438 UIUtil.drawVDottedLine((Graphics2D)g, x1, y1, y2, getBackground(), Color.gray);
1441 private Max computeMaxSize() {
1442 Max max = new Max();
1443 for (TabInfo eachInfo : myVisibleInfos) {
1444 final TabLabel label = myInfo2Label.get(eachInfo);
1445 max.myLabel.height = Math.max(max.myLabel.height, label.getPreferredSize().height);
1446 max.myLabel.width = Math.max(max.myLabel.width, label.getPreferredSize().width);
1447 final JComponent toolbar = myInfo2Toolbar.get(eachInfo);
1448 if (toolbar != null) {
1449 max.myToolbar.height = Math.max(max.myToolbar.height, toolbar.getPreferredSize().height);
1450 max.myToolbar.width = Math.max(max.myToolbar.width, toolbar.getPreferredSize().width);
1454 max.myToolbar.height++;
1456 return max;
1459 @Nullable
1460 private JComponent getSelectedComponent() {
1461 final TabInfo selection = getSelectedInfo();
1462 if (selection != null) {
1463 final JComponent c = selection.getComponent();
1464 if (c != null && c.getParent() == this) return c;
1467 return null;
1470 public Dimension getMinimumSize() {
1471 final JComponent c = getSelectedComponent();
1472 return c != null ? c.getMinimumSize() : new Dimension(0, 0);
1475 public Dimension getMaximumSize() {
1476 final JComponent c = getSelectedComponent();
1477 return c != null ? c.getMaximumSize() : super.getPreferredSize();
1480 public Dimension getPreferredSize() {
1481 final JComponent c = getSelectedComponent();
1482 return c != null ? c.getPreferredSize() : super.getPreferredSize();
1485 public int getTabCount() {
1486 return getTabs().size();
1489 @NotNull
1490 public JBTabsPresentation getPresentation() {
1491 return this;
1494 public ActionCallback removeTab(final JComponent component) {
1495 return removeTab(findInfo(component));
1498 public ActionCallback removeTab(final TabInfo info) {
1499 return removeTab(info, true);
1502 public ActionCallback removeTab(final TabInfo info, boolean transferFocus) {
1503 if (info == null || !getTabs().contains(info)) return new ActionCallback.Done();
1505 final ActionCallback result = new ActionCallback();
1507 TabInfo toSelect = transferFocus ? getToSelectOnRemoveOf(info) : null;
1510 if (toSelect != null) {
1511 final JComponent deferred = processRemove(info, false);
1512 _setSelected(toSelect, true).doWhenProcessed(new Runnable() {
1513 public void run() {
1514 removeDeferred(deferred);
1516 }).notifyWhenDone(result);
1518 else {
1519 removeDeferred(processRemove(info, true)).notifyWhenDone(result);
1522 if (myVisibleInfos.size() == 0) {
1523 removeCurrentDeferred();
1526 revalidateAndRepaint(true);
1528 return result;
1531 @Nullable
1532 private JComponent processRemove(final TabInfo info, boolean forcedNow) {
1533 remove(myInfo2Label.get(info));
1534 final JComponent tb = myInfo2Toolbar.get(info);
1535 if (tb != null) {
1536 remove(tb);
1539 JComponent tabComponent = info.getComponent();
1541 if (!isFocused(tabComponent) || forcedNow) {
1542 remove(tabComponent);
1543 tabComponent = null;
1545 else {
1546 setForDeferredRemove(tabComponent, true);
1549 myVisibleInfos.remove(info);
1550 myHiddenInfos.remove(info);
1551 myInfo2Label.remove(info);
1552 myInfo2Toolbar.remove(info);
1553 myAllTabs = null;
1555 updateAll(false, false);
1557 return tabComponent;
1560 public TabInfo findInfo(Component component) {
1561 for (TabInfo each : getTabs()) {
1562 if (each.getComponent() == component) return each;
1565 return null;
1568 public TabInfo findInfo(String text) {
1569 if (text == null) return null;
1571 for (TabInfo each : getTabs()) {
1572 if (text.equals(each.getText())) return each;
1575 return null;
1578 public TabInfo findInfo(MouseEvent event) {
1579 final Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this);
1580 return _findInfo(point, false);
1583 public TabInfo findInfo(final Object object) {
1584 for (int i = 0; i < getTabCount(); i++) {
1585 final TabInfo each = getTabAt(i);
1586 final Object eachObject = each.getObject();
1587 if (eachObject != null && eachObject.equals(object)) return each;
1589 return null;
1592 public TabInfo findTabLabelBy(final Point point) {
1593 return _findInfo(point, true);
1596 private TabInfo _findInfo(final Point point, boolean labelsOnly) {
1597 Component component = findComponentAt(point);
1598 if (component == null) return null;
1599 while (component != this || component != null) {
1600 if (component instanceof TabLabel) {
1601 return ((TabLabel)component).getInfo();
1603 else if (!labelsOnly) {
1604 final TabInfo info = findInfo(component);
1605 if (info != null) return info;
1607 if (component == null) break;
1608 component = component.getParent();
1611 return null;
1614 public void removeAllTabs() {
1615 for (TabInfo each : getTabs()) {
1616 removeTab(each);
1621 private class Max {
1622 Dimension myLabel = new Dimension();
1623 Dimension myToolbar = new Dimension();
1626 @Nullable
1627 private Component updateContainer(boolean forced, final boolean layoutNow) {
1628 Component deferredRemove = null;
1630 for (TabInfo each : myVisibleInfos) {
1631 final JComponent eachComponent = each.getComponent();
1632 if (getSelectedInfo() == each && getSelectedInfo() != null) {
1633 final Container parent = eachComponent.getParent();
1634 if (parent != null && parent != this) {
1635 parent.remove(eachComponent);
1638 if (eachComponent.getParent() == null) {
1639 add(eachComponent);
1642 else {
1643 if (eachComponent.getParent() == null) continue;
1644 if (isFocused(eachComponent)) {
1645 deferredRemove = eachComponent;
1647 else {
1648 remove(eachComponent);
1653 if (deferredRemove != null) {
1654 setForDeferredRemove(deferredRemove, true);
1658 relayout(forced, layoutNow);
1660 return deferredRemove;
1663 protected void addImpl(final Component comp, final Object constraints, final int index) {
1664 setForDeferredRemove(comp, false);
1666 if (comp instanceof TabLabel) {
1667 ((TabLabel)comp).apply(myUiDecorator.getDecoration());
1670 super.addImpl(comp, constraints, index);
1673 private boolean isFocused(JComponent c) {
1674 Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1675 return focusOwner != null && (focusOwner == c || SwingUtilities.isDescendingFrom(focusOwner, c));
1678 private void relayout(boolean forced, final boolean layoutNow) {
1679 if (!myForcedRelayout) {
1680 myForcedRelayout = forced;
1682 revalidateAndRepaint(layoutNow);
1685 ActionManager getActionManager() {
1686 return myActionManager;
1689 @NotNull
1690 public JBTabs addTabMouseListener(@NotNull MouseListener listener) {
1691 removeListeners();
1692 myTabMouseListeners.add(listener);
1693 addListeners();
1694 return this;
1697 @NotNull
1698 public JComponent getComponent() {
1699 return this;
1702 @NotNull
1703 public JBTabs removeTabMouseListener(@NotNull MouseListener listener) {
1704 removeListeners();
1705 myTabMouseListeners.remove(listener);
1706 addListeners();
1707 return this;
1710 private void addListeners() {
1711 for (TabInfo eachInfo : myVisibleInfos) {
1712 final TabLabel label = myInfo2Label.get(eachInfo);
1713 for (MouseListener eachListener : myTabMouseListeners) {
1714 label.addMouseListener(eachListener);
1719 private void removeListeners() {
1720 for (TabInfo eachInfo : myVisibleInfos) {
1721 final TabLabel label = myInfo2Label.get(eachInfo);
1722 for (MouseListener eachListener : myTabMouseListeners) {
1723 label.removeMouseListener(eachListener);
1728 private void updateListeners() {
1729 removeListeners();
1730 addListeners();
1733 public JBTabs addListener(@NotNull TabsListener listener) {
1734 myTabListeners.add(listener);
1735 return this;
1738 public JBTabs removeListener(@NotNull final TabsListener listener) {
1739 myTabListeners.remove(listener);
1740 return this;
1743 protected void onPopup(final TabInfo popupInfo) {
1746 public void setFocused(final boolean focused) {
1747 myFocused = focused;
1748 repaint();
1751 public int getIndexOf(@Nullable final TabInfo tabInfo) {
1752 return myVisibleInfos.indexOf(tabInfo);
1755 public boolean isHideTabs() {
1756 return myHideTabs;
1759 public void setHideTabs(final boolean hideTabs) {
1760 if (isHideTabs() == hideTabs) return;
1762 myHideTabs = hideTabs;
1764 relayout(true, false);
1767 public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) {
1768 if (myBorderSize.top == top && myBorderSize.left == left && myBorderSize.right == right && myBorderSize.bottom == bottom) return this;
1770 myBorderSize = new Insets(getBorder(top), getBorder(left), getBorder(bottom), getBorder(right));
1772 revalidateAndRepaint(false);
1774 return this;
1777 private static int getBorder(int size) {
1778 return size == -1 ? 1 : size;
1781 public boolean isPaintFocus() {
1782 return myPaintFocus;
1785 @NotNull
1786 public JBTabsPresentation setAdjustBorders(final boolean adjust) {
1787 myAdjustBorders = adjust;
1788 return this;
1791 @NotNull
1792 public JBTabsPresentation setActiveTabFillIn(@Nullable final Color color) {
1793 myActiveTabFillIn = color;
1794 revalidateAndRepaint(false);
1795 return this;
1798 @Nullable
1799 public Color getActiveTabFillIn() {
1800 return myActiveTabFillIn;
1803 public JBTabsPresentation setFocusCycle(final boolean root) {
1804 setFocusCycleRoot(root);
1805 return this;
1809 public JBTabsPresentation setPaintFocus(final boolean paintFocus) {
1810 myPaintFocus = paintFocus;
1811 return this;
1814 private static abstract class BaseNavigationAction extends AnAction {
1816 private ShadowAction myShadow;
1818 protected BaseNavigationAction(final String copyFromID, JComponent c) {
1819 myShadow = new ShadowAction(this, ActionManager.getInstance().getAction(copyFromID), c);
1822 public final void update(final AnActionEvent e) {
1823 JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
1824 e.getPresentation().setVisible(tabs != null);
1825 if (tabs == null) return;
1827 final int selectedIndex = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
1828 final boolean enabled = tabs.myVisibleInfos.size() > 0 && selectedIndex >= 0;
1829 e.getPresentation().setEnabled(enabled);
1830 if (enabled) {
1831 _update(e, tabs, selectedIndex);
1835 protected abstract void _update(AnActionEvent e, final JBTabsImpl tabs, int selectedIndex);
1837 public final void actionPerformed(final AnActionEvent e) {
1838 JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
1839 if (tabs == null) return;
1841 final int index = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
1842 if (index == -1) return;
1843 _actionPerformed(e, tabs, index);
1846 protected abstract void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex);
1849 private static class SelectNextAction extends BaseNavigationAction {
1851 public SelectNextAction(JComponent c) {
1852 super(IdeActions.ACTION_NEXT_TAB, c);
1855 protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
1856 e.getPresentation().setEnabled(tabs.myVisibleInfos.size() > 0 && selectedIndex < tabs.myVisibleInfos.size() - 1);
1859 protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
1860 tabs.select(tabs.myVisibleInfos.get(selectedIndex + 1), true);
1864 private static class SelectPreviousAction extends BaseNavigationAction {
1865 public SelectPreviousAction(JComponent c) {
1866 super(IdeActions.ACTION_PREVIOUS_TAB, c);
1869 protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
1870 e.getPresentation().setEnabled(tabs.myVisibleInfos.size() > 0 && selectedIndex > 0);
1873 protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
1874 tabs.select(tabs.myVisibleInfos.get(selectedIndex - 1), true);
1878 private void disposePopupListener() {
1879 if (myActivePopup != null) {
1880 myActivePopup.removePopupMenuListener(myPopupListener);
1881 myActivePopup = null;
1885 public JBTabsPresentation setStealthTabMode(final boolean stealthTabMode) {
1886 myStealthTabMode = stealthTabMode;
1888 relayout(true, false);
1890 return this;
1893 public boolean isStealthTabMode() {
1894 return myStealthTabMode;
1897 public JBTabsPresentation setSideComponentVertical(final boolean vertical) {
1898 myHorizontalSide = !vertical;
1900 for (TabInfo each : myVisibleInfos) {
1901 each.getChangeSupport().firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2");
1905 relayout(true, false);
1907 return this;
1910 public JBTabsPresentation setSingleRow(boolean singleRow) {
1911 myLayout = singleRow ? mySingleRowLayout : myTableLayout;
1913 relayout(true, false);
1915 return this;
1918 public JBTabsPresentation setGhostsAlwaysVisible(final boolean visible) {
1919 myGhostsAlwaysVisible = visible;
1921 relayout(true, false);
1923 return this;
1926 public boolean isGhostsAlwaysVisible() {
1927 return myGhostsAlwaysVisible;
1930 public boolean isSingleRow() {
1931 return myLayout == mySingleRowLayout;
1934 public boolean isSideComponentVertical() {
1935 return !myHorizontalSide;
1938 public JBTabsPresentation setUiDecorator(UiDecorator decorator) {
1939 myUiDecorator = decorator == null ? ourDefaultDecorator : decorator;
1940 applyDecoration();
1941 return this;
1944 protected void setUI(final ComponentUI newUI) {
1945 super.setUI(newUI);
1946 applyDecoration();
1949 public void updateUI() {
1950 super.updateUI();
1951 SwingUtilities.invokeLater(new Runnable() {
1952 public void run() {
1953 applyDecoration();
1955 revalidateAndRepaint(false);
1960 private void applyDecoration() {
1961 if (myUiDecorator != null) {
1962 UiDecorator.UiDecoration uiDecoration = myUiDecorator.getDecoration();
1963 for (TabLabel each : myInfo2Label.values()) {
1964 each.apply(uiDecoration);
1969 for (TabInfo each : getTabs()) {
1970 adjust(each);
1973 relayout(true, false);
1976 private void adjust(final TabInfo each) {
1977 if (myAdjustBorders) {
1978 UIUtil.removeScrollBorder(each.getComponent());
1982 public void sortTabs(Comparator<TabInfo> comparator) {
1983 Collections.sort(myVisibleInfos, comparator);
1985 relayout(true, false);
1988 public boolean isRequestFocusOnLastFocusedComponent() {
1989 return myRequestFocusOnLastFocusedComponent;
1992 public JBTabsPresentation setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent) {
1993 myRequestFocusOnLastFocusedComponent = requestFocusOnLastFocusedComponent;
1994 return this;
1998 @Nullable
1999 public Object getData(@NonNls final String dataId) {
2000 if (myDataProvider != null) {
2001 final Object value = myDataProvider.getData(dataId);
2002 if (value != null) return value;
2005 if (!NAVIGATION_ACTIONS_KEY.getName().equals(dataId)) return null;
2006 return isNavigationVisible() ? this : null;
2010 public DataProvider getDataProvider() {
2011 return myDataProvider;
2014 public JBTabsImpl setDataProvider(@NotNull final DataProvider dataProvider) {
2015 myDataProvider = dataProvider;
2016 return this;
2020 public boolean isSelectionClick(final MouseEvent e, boolean canBeQuick) {
2021 if (e.getClickCount() == 1 || canBeQuick) {
2022 if (!e.isPopupTrigger()) {
2023 return e.getButton() == MouseEvent.BUTTON1 && !e.isControlDown() && !e.isAltDown() && !e.isMetaDown();
2027 return false;
2031 private static class DefautDecorator implements UiDecorator {
2032 @NotNull
2033 public UiDecoration getDecoration() {
2034 return new UiDecoration(null, new Insets(2, 8, 2, 8));