IDEA-122792: do not schedule toolbar refresh for partially initialized target component
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / actionSystem / impl / ActionToolbarImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.actionSystem.impl;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.DataManager;
20 import com.intellij.ide.impl.DataManagerImpl;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.*;
23 import com.intellij.openapi.actionSystem.ex.ActionButtonLook;
24 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
25 import com.intellij.openapi.actionSystem.ex.AnActionListener;
26 import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.ModalityState;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
31 import com.intellij.openapi.project.DumbAwareRunnable;
32 import com.intellij.openapi.ui.popup.*;
33 import com.intellij.openapi.util.ActionCallback;
34 import com.intellij.openapi.util.Computable;
35 import com.intellij.openapi.util.Disposer;
36 import com.intellij.openapi.wm.IdeFocusManager;
37 import com.intellij.openapi.wm.WindowManager;
38 import com.intellij.openapi.wm.ex.WindowManagerEx;
39 import com.intellij.ui.ColorUtil;
40 import com.intellij.ui.Gray;
41 import com.intellij.ui.JBColor;
42 import com.intellij.ui.awt.RelativePoint;
43 import com.intellij.ui.awt.RelativeRectangle;
44 import com.intellij.ui.switcher.SwitchTarget;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.ui.JBUI;
47 import com.intellij.util.ui.UIUtil;
48 import com.intellij.util.ui.update.UiNotifyConnector;
49 import org.jetbrains.annotations.NotNull;
50
51 import javax.swing.*;
52 import javax.swing.border.EmptyBorder;
53 import java.awt.*;
54 import java.awt.event.ComponentAdapter;
55 import java.awt.event.ComponentEvent;
56 import java.awt.event.MouseEvent;
57 import java.util.ArrayList;
58 import java.util.LinkedList;
59 import java.util.List;
60
61 public class ActionToolbarImpl extends JPanel implements ActionToolbar {
62   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.impl.ActionToolbarImpl");
63
64   private static final List<ActionToolbarImpl> ourToolbars = new LinkedList<ActionToolbarImpl>();
65   private static final String RIGHT_ALIGN_KEY = "RIGHT_ALIGN";
66
67   public static void updateAllToolbarsImmediately() {
68     for (ActionToolbarImpl toolbar : new ArrayList<ActionToolbarImpl>(ourToolbars)) {
69       toolbar.updateActionsImmediately();
70       for (Component c : toolbar.getComponents()) {
71         if (c instanceof ActionButton) {
72           ((ActionButton)c).updateToolTipText();
73           ((ActionButton)c).updateIcon();
74         }
75       }
76     }
77   }
78
79   /**
80    * This array contains Rectangles which define bounds of the corresponding
81    * components in the toolbar. This list can be consider as a cache of the
82    * Rectangle objects that are used in calculation of preferred sizes and
83    * components layout.
84    */
85   private final List<Rectangle> myComponentBounds = new ArrayList<Rectangle>();
86
87   private Dimension myMinimumButtonSize = JBUI.emptySize();
88
89   /**
90    * @see ActionToolbar#getLayoutPolicy()
91    */
92   private int myLayoutPolicy;
93   private int myOrientation;
94   private final ActionGroup myActionGroup;
95   private final String myPlace;
96   protected List<AnAction> myVisibleActions;
97   private final PresentationFactory myPresentationFactory = new PresentationFactory();
98   private final boolean myDecorateButtons;
99
100   private final ToolbarUpdater myUpdater;
101
102   /**
103    * @see ActionToolbar#adjustTheSameSize(boolean)
104    */
105   private boolean myAdjustTheSameSize;
106
107   private final ActionButtonLook myButtonLook = null;
108   private final ActionButtonLook myMinimalButtonLook = new InplaceActionButtonLook();
109   private final DataManager myDataManager;
110   protected final ActionManagerEx myActionManager;
111
112   private Rectangle myAutoPopupRec;
113
114   private final DefaultActionGroup mySecondaryActions = new DefaultActionGroup();
115   private boolean myMinimalMode;
116   private boolean myForceUseMacEnhancements;
117
118   public ActionButton getSecondaryActionsButton() {
119     return mySecondaryActionsButton;
120   }
121
122   private ActionButton mySecondaryActionsButton;
123
124   private int myFirstOutsideIndex = -1;
125   private JBPopup myPopup;
126
127   private JComponent myTargetComponent;
128   private boolean myReservePlaceAutoPopupIcon = true;
129   private boolean myAddSeparatorFirst;
130
131   public ActionToolbarImpl(String place,
132                            @NotNull final ActionGroup actionGroup,
133                            boolean horizontal,
134                            @NotNull DataManager dataManager,
135                            @NotNull ActionManagerEx actionManager,
136                            @NotNull KeymapManagerEx keymapManager) {
137     this(place, actionGroup, horizontal, false, dataManager, actionManager, keymapManager, false);
138   }
139
140   public ActionToolbarImpl(String place,
141                            @NotNull ActionGroup actionGroup,
142                            boolean horizontal,
143                            boolean decorateButtons,
144                            @NotNull DataManager dataManager,
145                            @NotNull ActionManagerEx actionManager,
146                            @NotNull KeymapManagerEx keymapManager) {
147     this(place, actionGroup, horizontal, decorateButtons, dataManager, actionManager, keymapManager, false);
148   }
149
150   public ActionToolbarImpl(String place,
151                            @NotNull ActionGroup actionGroup,
152                            final boolean horizontal,
153                            final boolean decorateButtons,
154                            @NotNull DataManager dataManager,
155                            @NotNull ActionManagerEx actionManager,
156                            @NotNull KeymapManagerEx keymapManager,
157                            boolean updateActionsNow) {
158     super(null);
159     myActionManager = actionManager;
160     myPlace = place;
161     myActionGroup = actionGroup;
162     myVisibleActions = new ArrayList<AnAction>();
163     myDataManager = dataManager;
164     myDecorateButtons = decorateButtons;
165     myUpdater = new ToolbarUpdater(actionManager, keymapManager, this) {
166       @Override
167       protected void updateActionsImpl(boolean transparentOnly, boolean forced) {
168         ActionToolbarImpl.this.updateActionsImpl(transparentOnly, forced);
169       }
170     };
171
172     setLayout(new BorderLayout());
173     setOrientation(horizontal ? SwingConstants.HORIZONTAL : SwingConstants.VERTICAL);
174
175     mySecondaryActions.getTemplatePresentation().setIcon(AllIcons.General.SecondaryGroup);
176     mySecondaryActions.setPopup(true);
177
178     myUpdater.updateActions(updateActionsNow, false);
179
180     // If the panel doesn't handle mouse event then it will be passed to its parent.
181     // It means that if the panel is in sliding mode then the focus goes to the editor
182     // and panel will be automatically hidden.
183     enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.COMPONENT_EVENT_MASK | AWTEvent.CONTAINER_EVENT_MASK);
184     setMiniMode(false);
185   }
186
187   @Override
188   public void updateUI() {
189     super.updateUI();
190     for (Component component : getComponents()) {
191       tweakActionComponentUI(component);
192     }
193   }
194
195   @Override
196   public void addNotify() {
197     super.addNotify();
198     ourToolbars.add(this);
199
200     // should update action right on the showing, otherwise toolbar may not be displayed at all,
201     // since by default all updates are postponed until frame gets focused.  
202     updateActionsImmediately();
203   }
204
205   private boolean doMacEnhancementsForMainToolbar() {
206     return UIUtil.isUnderAquaLookAndFeel() && (ActionPlaces.MAIN_TOOLBAR.equals(myPlace) || myForceUseMacEnhancements);
207   }
208
209   public void setForceUseMacEnhancements(boolean useMacEnhancements) {
210     myForceUseMacEnhancements = useMacEnhancements;
211   }
212
213   private boolean isInsideNavBar() {
214     return ActionPlaces.NAVIGATION_BAR_TOOLBAR.equals(myPlace);
215   }
216
217   @Override
218   public void removeNotify() {
219     super.removeNotify();
220     ourToolbars.remove(this);
221   }
222
223   @Override
224   public JComponent getComponent() {
225     return this;
226   }
227
228   @Override
229   public int getLayoutPolicy() {
230     return myLayoutPolicy;                                             
231   }
232
233   @Override
234   public void setLayoutPolicy(final int layoutPolicy) {
235     if (layoutPolicy != NOWRAP_LAYOUT_POLICY && layoutPolicy != WRAP_LAYOUT_POLICY && layoutPolicy != AUTO_LAYOUT_POLICY) {
236       throw new IllegalArgumentException("wrong layoutPolicy: " + layoutPolicy);
237     }
238     myLayoutPolicy = layoutPolicy;
239   }
240
241   @Override
242   protected void paintComponent(final Graphics g) {
243     if (doMacEnhancementsForMainToolbar()) {
244       final Rectangle r = getBounds();
245       UIUtil.drawGradientHToolbarBackground(g, r.width, r.height);
246     } else {
247       super.paintComponent(g);
248     }
249
250     if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
251       if (myAutoPopupRec != null) {
252         if (myOrientation == SwingConstants.HORIZONTAL) {
253           final int dy = myAutoPopupRec.height / 2 - AllIcons.Ide.Link.getIconHeight() / 2;
254           AllIcons.Ide.Link.paintIcon(this, g, (int)myAutoPopupRec.getMaxX() - AllIcons.Ide.Link.getIconWidth() - 1, myAutoPopupRec.y + dy);
255         }
256         else {
257           final int dx = myAutoPopupRec.width / 2 - AllIcons.Ide.Link.getIconWidth() / 2;
258           AllIcons.Ide.Link.paintIcon(this, g, myAutoPopupRec.x + dx, (int)myAutoPopupRec.getMaxY() - AllIcons.Ide.Link.getIconWidth() - 1);
259         }
260       }
261     }
262   }
263
264   private void fillToolBar(final List<AnAction> actions, boolean layoutSecondaries) {
265     final List<AnAction> rightAligned = new ArrayList<AnAction>();
266     if (myAddSeparatorFirst) {
267       add(new MySeparator());
268     }
269     for (int i = 0; i < actions.size(); i++) {
270       final AnAction action = actions.get(i);
271       if (action instanceof RightAlignedToolbarAction) {
272         rightAligned.add(action);
273         continue;
274       }
275 //      if (action instanceof Separator && isNavBar()) {
276 //        continue;
277 //      }
278
279       //if (action instanceof ComboBoxAction) {
280       //  ((ComboBoxAction)action).setSmallVariant(true);
281       //}
282
283       if (layoutSecondaries) {
284         if (!myActionGroup.isPrimary(action)) {
285           mySecondaryActions.add(action);
286           continue;
287         }
288       }
289
290       if (action instanceof Separator) {
291         if (i > 0 && i < actions.size() - 1) {
292           add(new MySeparator());
293         }
294       }
295       else if (action instanceof CustomComponentAction) {
296         add(getCustomComponent(action));
297       }
298       else {
299         add(createToolbarButton(action));
300       }
301     }
302
303     if (mySecondaryActions.getChildrenCount() > 0) {
304       mySecondaryActionsButton = new ActionButton(mySecondaryActions, myPresentationFactory.getPresentation(mySecondaryActions), myPlace, getMinimumButtonSize());
305       mySecondaryActionsButton.setNoIconsInPopup(true);
306       add(mySecondaryActionsButton);
307     }
308
309     for (AnAction action : rightAligned) {
310       JComponent button = action instanceof CustomComponentAction ? getCustomComponent(action) : createToolbarButton(action);
311       button.putClientProperty(RIGHT_ALIGN_KEY, Boolean.TRUE);
312       add(button);
313     }
314     //if ((ActionPlaces.MAIN_TOOLBAR.equals(myPlace) || ActionPlaces.NAVIGATION_BAR_TOOLBAR.equals(myPlace))) {
315     //  final AnAction searchEverywhereAction = ActionManager.getInstance().getAction("SearchEverywhere");
316     //  if (searchEverywhereAction != null) {
317     //    try {
318     //      final CustomComponentAction searchEveryWhereAction = (CustomComponentAction)searchEverywhereAction;
319     //      final JComponent searchEverywhere = searchEveryWhereAction.createCustomComponent(searchEverywhereAction.getTemplatePresentation());
320     //      searchEverywhere.putClientProperty("SEARCH_EVERYWHERE", Boolean.TRUE);
321     //      add(searchEverywhere);
322     //    }
323     //    catch (Exception ignore) {}
324     //  }
325     //}
326   }
327
328   private JComponent getCustomComponent(AnAction action) {
329     Presentation presentation = myPresentationFactory.getPresentation(action);
330     JComponent customComponent = ((CustomComponentAction)action).createCustomComponent(presentation);
331     tweakActionComponentUI(customComponent);
332     presentation.putClientProperty(CustomComponentAction.CUSTOM_COMPONENT_PROPERTY, customComponent);
333     return customComponent;
334   }
335
336   private void tweakActionComponentUI(@NotNull Component actionComponent) {
337     if (ActionPlaces.EDITOR_TOOLBAR.equals(myPlace)) {
338       // tweak font & color for editor toolbar to match editor tabs style
339       actionComponent.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL));
340       actionComponent.setForeground(ColorUtil.dimmer(JBColor.BLACK));
341     }
342   }
343
344   private Dimension getMinimumButtonSize() {
345     return isInsideNavBar() ? NAVBAR_MINIMUM_BUTTON_SIZE : DEFAULT_MINIMUM_BUTTON_SIZE;
346   } 
347
348   public ActionButton createToolbarButton(final AnAction action, final ActionButtonLook look, final String place, final Presentation presentation, final Dimension minimumSize) {
349     if (action.displayTextInToolbar()) {
350       return new ActionButtonWithText(action, presentation, place, minimumSize);
351     }
352
353     final ActionButton actionButton = new ActionButton(action, presentation, place, minimumSize) {
354       @Override
355       protected DataContext getDataContext() {
356         return getToolbarDataContext();
357       }
358     };
359     actionButton.setLook(look);
360     return actionButton;
361   }
362
363   private ActionButton createToolbarButton(final AnAction action) {
364     return createToolbarButton(
365       action,
366       myMinimalMode ? myMinimalButtonLook : myDecorateButtons ? new MacToolbarDecoratorButtonLook() : myButtonLook,
367       myPlace, myPresentationFactory.getPresentation(action),
368       myMinimumButtonSize);
369   }
370
371   @Override
372   public void doLayout() {
373     if (!isValid()) {
374       calculateBounds(getSize(), myComponentBounds);
375     }
376     final int componentCount = getComponentCount();
377     LOG.assertTrue(componentCount <= myComponentBounds.size());
378     for (int i = componentCount - 1; i >= 0; i--) {
379       final Component component = getComponent(i);
380       component.setBounds(myComponentBounds.get(i));
381     }
382   }
383
384   @Override
385   public void validate() {
386     if (!isValid()) {
387       calculateBounds(getSize(), myComponentBounds);
388       super.validate();
389     }
390   }
391
392   private Dimension getChildPreferredSize(int index) {
393     Component component = getComponent(index);
394     return component.isVisible() ? component.getPreferredSize() : new Dimension();
395   }
396
397   /**
398    * @return maximum button width
399    */
400   private int getMaxButtonWidth() {
401     int width = 0;
402     for (int i = 0; i < getComponentCount(); i++) {
403       final Dimension dimension = getChildPreferredSize(i);
404       width = Math.max(width, dimension.width);
405     }
406     return width;
407   }
408
409   /**
410    * @return maximum button height
411    */
412   @Override
413   public int getMaxButtonHeight() {
414     int height = 0;
415     for (int i = 0; i < getComponentCount(); i++) {
416       final Dimension dimension = getChildPreferredSize(i);
417       height = Math.max(height, dimension.height);
418     }
419     return height;
420   }
421
422   private void calculateBoundsNowrapImpl(List<Rectangle> bounds) {
423     final int componentCount = getComponentCount();
424     LOG.assertTrue(componentCount <= bounds.size());
425
426     final int width = getWidth();
427     final int height = getHeight();
428
429     final Insets insets = getInsets();
430
431     if (myAdjustTheSameSize) {
432       final int maxWidth = getMaxButtonWidth();
433       final int maxHeight = getMaxButtonHeight();
434
435       if (myOrientation == SwingConstants.HORIZONTAL) {
436         int xOffset = insets.left;
437         for (int i = 0; i < componentCount; i++) {
438           final Rectangle r = bounds.get(i);
439           r.setBounds(xOffset, (height - maxHeight) / 2, maxWidth, maxHeight);
440           xOffset += maxWidth;
441         }
442       }
443       else {
444         int yOffset = insets.top;
445         for (int i = 0; i < componentCount; i++) {
446           final Rectangle r = bounds.get(i);
447           r.setBounds((width - maxWidth) / 2, yOffset, maxWidth, maxHeight);
448           yOffset += maxHeight;
449         }
450       }
451     }
452     else {
453       if (myOrientation == SwingConstants.HORIZONTAL) {
454         final int maxHeight = getMaxButtonHeight();
455
456         int xOffset = insets.left;
457         final int yOffset = insets.top;
458         for (int i = 0; i < componentCount; i++) {
459           final Dimension d = getChildPreferredSize(i);
460           final Rectangle r = bounds.get(i);
461           r.setBounds(xOffset, yOffset + (maxHeight - d.height) / 2, d.width, d.height);
462           xOffset += d.width;
463         }
464       }
465       else {
466         final int maxWidth = getMaxButtonWidth();
467         final int xOffset = insets.left;
468         int yOffset = insets.top;
469         for (int i = 0; i < componentCount; i++) {
470           final Dimension d = getChildPreferredSize(i);
471           final Rectangle r = bounds.get(i);
472           r.setBounds(xOffset + (maxWidth - d.width) / 2, yOffset, d.width, d.height);
473           yOffset += d.height;
474         }
475       }
476     }
477   }
478
479   private void calculateBoundsAutoImp(Dimension sizeToFit, List<Rectangle> bounds) {
480     final int componentCount = getComponentCount();
481     LOG.assertTrue(componentCount <= bounds.size());
482
483     final boolean actualLayout = bounds == myComponentBounds;
484
485     if (actualLayout) {
486       myAutoPopupRec = null;
487     }
488                
489     int autoButtonSize = AllIcons.Ide.Link.getIconWidth();
490     boolean full = false;
491
492     final Insets insets = getInsets();
493
494     if (myOrientation == SwingConstants.HORIZONTAL) {
495       int eachX = insets.left;
496       int eachY = insets.top;
497       int maxHeight = 0;
498       for (int i = 0; i < componentCount; i++) {
499         final Component eachComp = getComponent(i);
500         final boolean isLast = i == componentCount - 1;
501
502         final Rectangle eachBound = new Rectangle(getChildPreferredSize(i));
503         maxHeight = Math.max(eachBound.height, maxHeight);
504
505         if (!full) {
506           boolean inside;
507           if (isLast) {
508             inside = eachX + eachBound.width <= sizeToFit.width;
509           } else {
510             inside = eachX + eachBound.width + autoButtonSize <= sizeToFit.width;
511           }
512
513           if (inside) {
514             if (eachComp == mySecondaryActionsButton) {
515               assert isLast;
516               if (sizeToFit.width != Integer.MAX_VALUE) {
517                 eachBound.x = sizeToFit.width - eachBound.width;
518                 eachX = (int)eachBound.getMaxX();
519               }
520               else {
521                 eachBound.x = eachX;
522               }
523             } else {
524               eachBound.x = eachX;
525               eachX += eachBound.width;
526             }
527             eachBound.y = eachY;
528           }
529           else {
530             full = true;
531           }
532         }
533
534         if (full) {
535           if (myAutoPopupRec == null) {
536             myAutoPopupRec = new Rectangle(eachX, eachY, sizeToFit.width - eachX - 1, sizeToFit.height - 1);
537             myFirstOutsideIndex = i;
538           }
539           eachBound.x = Integer.MAX_VALUE;
540           eachBound.y = Integer.MAX_VALUE;
541         }
542
543         bounds.get(i).setBounds(eachBound);
544       }
545
546       for (final Rectangle r : bounds) {
547         if (r.height < maxHeight) {
548           r.y += (maxHeight - r.height) / 2;
549         }
550       }
551
552     }
553     else {
554       int eachX = insets.left;
555       int eachY = insets.top;
556       for (int i = 0; i < componentCount; i++) {
557         final Rectangle eachBound = new Rectangle(getChildPreferredSize(i));
558         if (!full) {
559           boolean outside;
560           if (i < componentCount - 1) {
561             outside = eachY + eachBound.height + autoButtonSize < sizeToFit.height;
562           }
563           else {
564             outside = eachY + eachBound.height < sizeToFit.height;
565           }
566           if (outside) {
567             eachBound.x = eachX;
568             eachBound.y = eachY;
569             eachY += eachBound.height;
570           }
571           else {
572             full = true;
573           }
574         }
575
576         if (full) {
577           if (myAutoPopupRec == null) {
578             myAutoPopupRec = new Rectangle(eachX, eachY, sizeToFit.width - 1, sizeToFit.height - eachY - 1);
579             myFirstOutsideIndex = i;
580           }
581           eachBound.x = Integer.MAX_VALUE;
582           eachBound.y = Integer.MAX_VALUE;
583         }
584
585         bounds.get(i).setBounds(eachBound);
586       }
587     }
588
589   }
590
591   private void calculateBoundsWrapImpl(Dimension sizeToFit, List<Rectangle> bounds) {
592     // We have to graceful handle case when toolbar was not laid out yet.
593     // In this case we calculate bounds as it is a NOWRAP toolbar.
594     if (getWidth() == 0 || getHeight() == 0) {
595       try {
596         setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
597         calculateBoundsNowrapImpl(bounds);
598       }
599       finally {
600         setLayoutPolicy(WRAP_LAYOUT_POLICY);
601       }
602       return;
603     }
604
605
606     final int componentCount = getComponentCount();
607     LOG.assertTrue(componentCount <= bounds.size());
608
609     final Insets insets = getInsets();
610
611     if (myAdjustTheSameSize) {
612       if (myOrientation == SwingConstants.HORIZONTAL) {
613         final int maxWidth = getMaxButtonWidth();
614         final int maxHeight = getMaxButtonHeight();
615
616         // Lay components out
617         int xOffset = insets.left;
618         int yOffset = insets.top;
619         // Calculate max size of a row. It's not possible to make more than 3 row toolbar
620         final int maxRowWidth = Math.max(sizeToFit.width, componentCount * maxWidth / 3);
621         for (int i = 0; i < componentCount; i++) {
622           if (xOffset + maxWidth > maxRowWidth) { // place component at new row
623             xOffset = insets.left;
624             yOffset += maxHeight;
625           }
626
627           final Rectangle each = bounds.get(i);
628           each.setBounds(xOffset, yOffset, maxWidth, maxHeight);
629
630           xOffset += maxWidth;
631         }
632       }
633       else {
634         final int maxWidth = getMaxButtonWidth();
635         final int maxHeight = getMaxButtonHeight();
636
637         // Lay components out
638         int xOffset = insets.left;
639         int yOffset = insets.top;
640         // Calculate max size of a row. It's not possible to make more then 3 column toolbar
641         final int maxRowHeight = Math.max(sizeToFit.height, componentCount * myMinimumButtonSize.height / 3);
642         for (int i = 0; i < componentCount; i++) {
643           if (yOffset + maxHeight > maxRowHeight) { // place component at new row
644             yOffset = insets.top;
645             xOffset += maxWidth;
646           }
647
648           final Rectangle each = bounds.get(i);
649           each.setBounds(xOffset, yOffset, maxWidth, maxHeight);
650
651           yOffset += maxHeight;
652         }
653       }
654     }
655     else {
656       if (myOrientation == SwingConstants.HORIZONTAL) {
657         // Calculate row height
658         int rowHeight = 0;
659         final Dimension[] dims = new Dimension[componentCount]; // we will use this dimensions later
660         for (int i = 0; i < componentCount; i++) {
661           dims[i] = getChildPreferredSize(i);
662           final int height = dims[i].height;
663           rowHeight = Math.max(rowHeight, height);
664         }
665
666         // Lay components out
667         int xOffset = insets.left;
668         int yOffset = insets.top;
669         // Calculate max size of a row. It's not possible to make more then 3 row toolbar
670         final int maxRowWidth = Math.max(getWidth(), componentCount * myMinimumButtonSize.width / 3);
671         for (int i = 0; i < componentCount; i++) {
672           final Dimension d = dims[i];
673           if (xOffset + d.width > maxRowWidth) { // place component at new row
674             xOffset = insets.left;
675             yOffset += rowHeight;
676           }
677
678           final Rectangle each = bounds.get(i);
679           each.setBounds(xOffset, yOffset + (rowHeight - d.height) / 2, d.width, d.height);
680
681           xOffset += d.width;
682         }
683       }
684       else {
685         // Calculate row width
686         int rowWidth = 0;
687         final Dimension[] dims = new Dimension[componentCount]; // we will use this dimensions later
688         for (int i = 0; i < componentCount; i++) {
689           dims[i] = getChildPreferredSize(i);
690           final int width = dims[i].width;
691           rowWidth = Math.max(rowWidth, width);
692         }
693
694         // Lay components out
695         int xOffset = insets.left;
696         int yOffset = insets.top;
697         // Calculate max size of a row. It's not possible to make more then 3 column toolbar
698         final int maxRowHeight = Math.max(getHeight(), componentCount * myMinimumButtonSize.height / 3);
699         for (int i = 0; i < componentCount; i++) {
700           final Dimension d = dims[i];
701           if (yOffset + d.height > maxRowHeight) { // place component at new row
702             yOffset = insets.top;
703             xOffset += rowWidth;
704           }
705
706           final Rectangle each = bounds.get(i);
707           each.setBounds(xOffset + (rowWidth - d.width) / 2, yOffset, d.width, d.height);
708
709           yOffset += d.height;
710         }
711       }
712     }
713   }
714
715   /**
716    * Calculates bounds of all the components in the toolbar
717    */
718   private void calculateBounds(Dimension size2Fit, List<Rectangle> bounds) {
719     bounds.clear();
720     for (int i = 0; i < getComponentCount(); i++) {
721       bounds.add(new Rectangle());
722     }
723
724     if (myLayoutPolicy == NOWRAP_LAYOUT_POLICY) {
725       calculateBoundsNowrapImpl(bounds);
726     }
727     else if (myLayoutPolicy == WRAP_LAYOUT_POLICY) {
728       calculateBoundsWrapImpl(size2Fit, bounds);
729     }
730     else if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
731       calculateBoundsAutoImp(size2Fit, bounds);
732     }
733     else {
734       throw new IllegalStateException("unknown layoutPolicy: " + myLayoutPolicy);
735     }
736
737
738     if (getComponentCount() > 0 && size2Fit.width < Integer.MAX_VALUE) {
739       int maxHeight = 0;
740       for (int i = 0; i < bounds.size() - 2; i++) {
741         maxHeight = Math.max(maxHeight, bounds.get(i).height);
742       }
743
744       for (int i = getComponentCount() - 1, j = 1; i > 0; i--, j++) {
745         final Component component = getComponent(i);
746         if (component instanceof JComponent && ((JComponent)component).getClientProperty(RIGHT_ALIGN_KEY) == Boolean.TRUE) {
747           bounds.set(bounds.size() - j, new Rectangle(size2Fit.width - j * JBUI.scale(25), 0, JBUI.scale(25), maxHeight));
748         }
749       }
750     }
751   }
752
753   @Override
754   public Dimension getPreferredSize() {
755     final ArrayList<Rectangle> bounds = new ArrayList<Rectangle>();
756     calculateBounds(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE), bounds);
757     if (bounds.isEmpty()) return JBUI.emptySize();
758     int xLeft = Integer.MAX_VALUE;
759     int yTop = Integer.MAX_VALUE;
760     int xRight = Integer.MIN_VALUE;
761     int yBottom = Integer.MIN_VALUE;
762     for (int i = bounds.size() - 1; i >= 0; i--) {
763       final Rectangle each = bounds.get(i);
764       if (each.x == Integer.MAX_VALUE) continue;
765       xLeft = Math.min(xLeft, each.x);
766       yTop = Math.min(yTop, each.y);
767       xRight = Math.max(xRight, each.x + each.width);
768       yBottom = Math.max(yBottom, each.y + each.height);
769     }
770     final Dimension dimension = new Dimension(xRight - xLeft, yBottom - yTop);
771
772     if (myLayoutPolicy == AUTO_LAYOUT_POLICY && myReservePlaceAutoPopupIcon) {
773       if (myOrientation == SwingConstants.HORIZONTAL) {
774         dimension.width += AllIcons.Ide.Link.getIconWidth();
775       }
776       else {
777         dimension.height += AllIcons.Ide.Link.getIconHeight();
778       }
779     }
780
781     final Insets i = getInsets();
782
783     return new Dimension(dimension.width + i.left + i.right, dimension.height + i.top + i.bottom);
784   }
785
786   @Override
787   public Dimension getMinimumSize() {
788     if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
789       final Insets i = getInsets();
790       return new Dimension(AllIcons.Ide.Link.getIconWidth() + i.left + i.right, myMinimumButtonSize.height + i.top + i.bottom);
791     }
792     else {
793       return super.getMinimumSize();
794     }
795   }
796
797   private final class MySeparator extends JComponent {
798     private final Dimension mySize;
799
800     public MySeparator() {
801       if (myOrientation == SwingConstants.HORIZONTAL) {
802         mySize = JBUI.size(6, 24);
803       }
804       else {
805         mySize = JBUI.size(24, 6);
806       }
807     }
808
809     @Override
810     public Dimension getPreferredSize() {
811       return mySize;
812     }
813
814     @Override
815     protected void paintComponent(final Graphics g) {
816       final Insets i = getInsets();
817       if (UIUtil.isUnderAquaBasedLookAndFeel() || UIUtil.isUnderDarcula()) {
818         if (getParent() != null) {
819           final JBColor col = new JBColor(Gray._128, Gray._111);
820           final Graphics2D g2 = (Graphics2D)g;
821           if (myOrientation == SwingConstants.HORIZONTAL) {
822             UIUtil.drawDoubleSpaceDottedLine(g2, i.top + 2, getParent().getSize().height - 2 - i.top - i.bottom, 3, col, false);
823           } else {
824             UIUtil.drawDoubleSpaceDottedLine(g2, i.left + 2, getParent().getSize().width - 2 - i.left - i.right, 3, col, true);
825           }
826         }
827       }
828       else {
829         g.setColor(UIUtil.getSeparatorColor());
830         if (getParent() != null) {
831           if (myOrientation == SwingConstants.HORIZONTAL) {
832             UIUtil.drawLine(g, 3, 2, 3, getParent().getSize().height - 2);
833           }
834           else {
835             UIUtil.drawLine(g, 2, 3, getParent().getSize().width - 2, 3);
836           }
837         }
838       }
839     }
840   }
841
842   @Override
843   public void adjustTheSameSize(final boolean value) {
844     if (myAdjustTheSameSize == value) {
845       return;
846     }
847     myAdjustTheSameSize = value;
848     revalidate();
849   }
850
851   @Override
852   public void setMinimumButtonSize(@NotNull final Dimension size) {
853     myMinimumButtonSize = size;
854     for (int i = getComponentCount() - 1; i >= 0; i--) {
855       final Component component = getComponent(i);
856       if (component instanceof ActionButton) {
857         final ActionButton button = (ActionButton)component;
858         button.setMinimumButtonSize(size);
859       }
860     }
861     revalidate();
862   }
863
864   @Override
865   public void setOrientation(final int orientation) {
866     if (SwingConstants.HORIZONTAL != orientation && SwingConstants.VERTICAL != orientation) {
867       throw new IllegalArgumentException("wrong orientation: " + orientation);
868     }
869     myOrientation = orientation;
870   }
871
872   @Override
873   public void updateActionsImmediately() {
874     ApplicationManager.getApplication().assertIsDispatchThread();
875     myUpdater.updateActions(true, false);
876   }
877
878   private void updateActionsImpl(boolean transparentOnly, boolean forced) {
879     List<AnAction> newVisibleActions = ContainerUtil.newArrayListWithCapacity(myVisibleActions.size());
880     DataContext dataContext = getDataContext();
881
882     Utils.expandActionGroup(myActionGroup, newVisibleActions, myPresentationFactory, dataContext,
883                             myPlace, myActionManager, transparentOnly);
884
885     if (forced || !newVisibleActions.equals(myVisibleActions)) {
886       boolean shouldRebuildUI = newVisibleActions.isEmpty() || myVisibleActions.isEmpty();
887       myVisibleActions = newVisibleActions;
888
889       Dimension oldSize = getPreferredSize();
890
891       removeAll();
892       mySecondaryActions.removeAll();
893       mySecondaryActionsButton = null;
894       fillToolBar(myVisibleActions, getLayoutPolicy() == AUTO_LAYOUT_POLICY && myOrientation == SwingConstants.HORIZONTAL);
895
896       Dimension newSize = getPreferredSize();
897
898       ((WindowManagerEx)WindowManager.getInstance()).adjustContainerWindow(this, oldSize, newSize);
899
900       if (shouldRebuildUI) {
901         revalidate();
902       }
903       else {
904         Container parent = getParent();
905         if (parent != null) {
906           parent.invalidate();
907           parent.validate();
908         }
909       }
910
911       repaint();
912     }
913   }
914
915
916   @Override
917   public boolean hasVisibleActions() {
918     return !myVisibleActions.isEmpty();
919   }
920
921   @Override
922   public void setTargetComponent(final JComponent component) {
923     myTargetComponent = component;
924
925     if (myTargetComponent != null) {
926       UiNotifyConnector.doWhenFirstShown(myTargetComponent, new DumbAwareRunnable() {
927         @Override
928         public void run() {
929           myUpdater.updateActions(false, false);
930         }
931       });
932     }
933   }
934
935   @Override
936   public DataContext getToolbarDataContext() {
937     return getDataContext();
938   }
939
940   protected DataContext getDataContext() {
941     return myTargetComponent != null ? myDataManager.getDataContext(myTargetComponent) : ((DataManagerImpl)myDataManager).getDataContextTest(this);
942   }
943
944   @Override
945   protected void processMouseMotionEvent(final MouseEvent e) {
946     super.processMouseMotionEvent(e);
947
948     if (getLayoutPolicy() != AUTO_LAYOUT_POLICY) {
949       return;
950     }
951     if (myAutoPopupRec != null && myAutoPopupRec.contains(e.getPoint())) {
952       IdeFocusManager.getInstance(null).doWhenFocusSettlesDown(new Runnable() {
953         @Override
954         public void run() {
955           showAutoPopup();
956         }
957       });
958     }
959   }
960
961   private void showAutoPopup() {
962     if (isPopupShowing()) return;
963
964     final ActionGroup group;
965     if (myOrientation == SwingConstants.HORIZONTAL) {
966       group = myActionGroup;
967     }
968     else {
969       final DefaultActionGroup outside = new DefaultActionGroup();
970       for (int i = myFirstOutsideIndex; i < myVisibleActions.size(); i++) {
971         outside.add(myVisibleActions.get(i));
972       }
973       group = outside;
974     }
975
976     PopupToolbar popupToolbar = new PopupToolbar(myPlace, group, true, myDataManager, myActionManager, myUpdater.getKeymapManager(), this) {
977       @Override
978       protected void onOtherActionPerformed() {
979         hidePopup();
980       }
981
982       @Override
983       protected DataContext getDataContext() {
984         return ActionToolbarImpl.this.getDataContext();
985       }
986     };
987     popupToolbar.setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
988     popupToolbar.updateActionsImmediately();
989
990     Point location;
991     if (myOrientation == SwingConstants.HORIZONTAL) {
992       location = getLocationOnScreen();
993     }
994     else {
995       location = getLocationOnScreen();
996       location.y = location.y + getHeight() - popupToolbar.getPreferredSize().height;
997     }
998
999
1000     final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(popupToolbar, null);
1001     builder.setResizable(false)
1002       .setMovable(true) // fit the screen automatically
1003       .setRequestFocus(false)
1004       .setTitle(null)
1005       .setCancelOnClickOutside(true)
1006       .setCancelOnOtherWindowOpen(true)
1007       .setCancelCallback(new Computable<Boolean>() {
1008         @Override
1009         public Boolean compute() {
1010           final boolean toClose = myActionManager.isActionPopupStackEmpty();
1011           if (toClose) {
1012             myUpdater.updateActions(false, true);
1013           }
1014           return toClose;
1015         }
1016       })
1017       .setCancelOnMouseOutCallback(new MouseChecker() {
1018         @Override
1019         public boolean check(final MouseEvent event) {
1020           return myAutoPopupRec != null &&
1021                  myActionManager.isActionPopupStackEmpty() &&
1022                  !new RelativeRectangle(ActionToolbarImpl.this, myAutoPopupRec).contains(new RelativePoint(event));
1023         }
1024       });
1025
1026     builder.addListener(new JBPopupAdapter() {
1027       @Override
1028       public void onClosed(LightweightWindowEvent event) {
1029         processClosed();
1030       }
1031     });
1032     myPopup = builder.createPopup();
1033     final AnActionListener.Adapter listener = new AnActionListener.Adapter() {
1034       @Override
1035       public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
1036         final JBPopup popup = myPopup;
1037         if (popup != null && !popup.isDisposed() && popup.isVisible()) {
1038           popup.cancel();
1039         }
1040       }
1041     };
1042     ActionManager.getInstance().addAnActionListener(listener);
1043     Disposer.register(myPopup, popupToolbar);
1044     Disposer.register(popupToolbar, new Disposable() {
1045       @Override
1046       public void dispose() {
1047         ActionManager.getInstance().removeAnActionListener(listener);
1048       }
1049     });
1050
1051     myPopup.showInScreenCoordinates(this, location);
1052
1053     final Window window = SwingUtilities.getWindowAncestor(this);
1054     if (window != null) {
1055       final ComponentAdapter componentAdapter = new ComponentAdapter() {
1056         @Override
1057         public void componentResized(final ComponentEvent e) {
1058           hidePopup();
1059         }
1060
1061         @Override
1062         public void componentMoved(final ComponentEvent e) {
1063           hidePopup();
1064         }
1065
1066         @Override
1067         public void componentShown(final ComponentEvent e) {
1068           hidePopup();
1069         }
1070
1071         @Override
1072         public void componentHidden(final ComponentEvent e) {
1073           hidePopup();
1074         }
1075       };
1076       window.addComponentListener(componentAdapter);
1077       Disposer.register(popupToolbar, new Disposable() {
1078         @Override
1079         public void dispose() {
1080           window.removeComponentListener(componentAdapter);
1081         }
1082       });
1083     }
1084   }
1085
1086
1087   private boolean isPopupShowing() {
1088     if (myPopup != null) {
1089       if (myPopup.getContent() != null) {
1090         return true;
1091       }
1092     }
1093     return false;
1094   }
1095
1096   private void hidePopup() {
1097     if (myPopup != null) {
1098       myPopup.cancel();
1099       processClosed();
1100     }
1101   }
1102
1103   private void processClosed() {
1104     if (myPopup == null) return;
1105
1106     Disposer.dispose(myPopup);
1107     myPopup = null;
1108
1109     myUpdater.updateActions(false, false);
1110   }
1111
1112   abstract static class PopupToolbar extends ActionToolbarImpl implements AnActionListener, Disposable {
1113     private final JComponent myParent;
1114
1115     public PopupToolbar(final String place,
1116                         final ActionGroup actionGroup,
1117                         final boolean horizontal,
1118                         final DataManager dataManager,
1119                         @NotNull ActionManagerEx actionManager,
1120                         final KeymapManagerEx keymapManager,
1121                         JComponent parent) {
1122       super(place, actionGroup, horizontal, false, dataManager, actionManager, keymapManager, true);
1123       myActionManager.addAnActionListener(this);
1124       myParent = parent;
1125     }
1126
1127     @Override
1128     public Container getParent() {
1129       Container parent = super.getParent();
1130       return parent != null ? parent : myParent;
1131     }
1132
1133     @Override
1134     public void dispose() {
1135       myActionManager.removeAnActionListener(this);
1136     }
1137
1138     @Override
1139     public void beforeActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
1140     }
1141
1142     @Override
1143     public void afterActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
1144       if (!myVisibleActions.contains(action)) {
1145         onOtherActionPerformed();
1146       }
1147     }
1148
1149     protected abstract void onOtherActionPerformed();
1150
1151     @Override
1152     public void beforeEditorTyping(final char c, final DataContext dataContext) {
1153     }
1154   }
1155
1156
1157   @Override
1158   public void setReservePlaceAutoPopupIcon(final boolean reserve) {
1159     myReservePlaceAutoPopupIcon = reserve;
1160   }
1161
1162   @Override
1163   public void setSecondaryActionsTooltip(String secondaryActionsTooltip) {
1164     mySecondaryActions.getTemplatePresentation().setDescription(secondaryActionsTooltip);
1165   }
1166
1167   @Override
1168   public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
1169     ArrayList<SwitchTarget> result = new ArrayList<SwitchTarget>();
1170
1171     if (getBounds().width * getBounds().height <= 0) return result;
1172
1173     for (int i = 0; i < getComponentCount(); i++) {
1174       Component each = getComponent(i);
1175       if (each instanceof ActionButton) {
1176         result.add(new ActionTarget((ActionButton)each));
1177       }
1178     }
1179     return result;
1180   }
1181
1182   private static class ActionTarget implements SwitchTarget {
1183     private final ActionButton myButton;
1184
1185     private ActionTarget(ActionButton button) {
1186       myButton = button;
1187     }
1188
1189     @Override
1190     public ActionCallback switchTo(boolean requestFocus) {
1191       myButton.click();
1192       return new ActionCallback.Done();
1193     }
1194
1195     @Override
1196     public boolean isVisible() {
1197       return myButton.isVisible();
1198     }
1199
1200     @Override
1201     public RelativeRectangle getRectangle() {
1202       return new RelativeRectangle(myButton.getParent(), myButton.getBounds());
1203     }
1204
1205     @Override
1206     public Component getComponent() {
1207       return myButton;
1208     }
1209
1210     @Override
1211     public String toString() {
1212       return myButton.getAction().toString();
1213     }
1214   }
1215
1216   @Override
1217   public SwitchTarget getCurrentTarget() {
1218     return null;
1219   }
1220
1221   @Override
1222   public boolean isCycleRoot() {
1223     return false;
1224   }
1225
1226   @Override
1227   public List<AnAction> getActions(boolean originalProvider) {
1228     ArrayList<AnAction> result = new ArrayList<AnAction>();
1229
1230     ArrayList<AnAction> secondary = new ArrayList<AnAction>();
1231     AnAction[] kids = myActionGroup.getChildren(null);
1232     for (AnAction each : kids) {
1233       if (myActionGroup.isPrimary(each)) {
1234         result.add(each);
1235       } else {
1236         secondary.add(each);
1237       }
1238     }
1239     result.add(new Separator());
1240     result.addAll(secondary);
1241
1242     return result;
1243   }
1244
1245   @Override
1246   public void setMiniMode(boolean minimalMode) {
1247     //if (myMinimalMode == minimalMode) return;
1248
1249     myMinimalMode = minimalMode;
1250     if (myMinimalMode) {
1251       setMinimumButtonSize(JBUI.emptySize());
1252       setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
1253       setBorder(new EmptyBorder(0, 0, 0, 0));
1254       setOpaque(false);
1255     } else {
1256       if (isInsideNavBar()) {
1257         setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
1258       }
1259       else {
1260         setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
1261       }
1262
1263       setMinimumButtonSize(myDecorateButtons ? new Dimension(30, 20) : DEFAULT_MINIMUM_BUTTON_SIZE);
1264       setOpaque(true);
1265       setLayoutPolicy(AUTO_LAYOUT_POLICY);
1266     }
1267
1268     myUpdater.updateActions(false, true);
1269   }
1270
1271   public void setAddSeparatorFirst(boolean addSeparatorFirst) {
1272     myAddSeparatorFirst = addSeparatorFirst;
1273     myUpdater.updateActions(false, true);
1274   }
1275 }