platofrm: set fsnotifier glibc compatibility only for i386/amd64
[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       .setMayBeParent(true) // a dialog-based popup is not always on top
1004       .setRequestFocus(false)
1005       .setTitle(null)
1006       .setCancelOnClickOutside(true)
1007       .setCancelOnOtherWindowOpen(true)
1008       .setCancelCallback(new Computable<Boolean>() {
1009         @Override
1010         public Boolean compute() {
1011           final boolean toClose = myActionManager.isActionPopupStackEmpty();
1012           if (toClose) {
1013             myUpdater.updateActions(false, true);
1014           }
1015           return toClose;
1016         }
1017       })
1018       .setCancelOnMouseOutCallback(new MouseChecker() {
1019         @Override
1020         public boolean check(final MouseEvent event) {
1021           return myAutoPopupRec != null &&
1022                  myActionManager.isActionPopupStackEmpty() &&
1023                  !new RelativeRectangle(ActionToolbarImpl.this, myAutoPopupRec).contains(new RelativePoint(event));
1024         }
1025       });
1026
1027     builder.addListener(new JBPopupAdapter() {
1028       @Override
1029       public void onClosed(LightweightWindowEvent event) {
1030         processClosed();
1031       }
1032     });
1033     myPopup = builder.createPopup();
1034     final AnActionListener.Adapter listener = new AnActionListener.Adapter() {
1035       @Override
1036       public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
1037         final JBPopup popup = myPopup;
1038         if (popup != null && !popup.isDisposed() && popup.isVisible()) {
1039           popup.cancel();
1040         }
1041       }
1042     };
1043     ActionManager.getInstance().addAnActionListener(listener);
1044     Disposer.register(myPopup, popupToolbar);
1045     Disposer.register(popupToolbar, new Disposable() {
1046       @Override
1047       public void dispose() {
1048         ActionManager.getInstance().removeAnActionListener(listener);
1049       }
1050     });
1051
1052     myPopup.showInScreenCoordinates(this, location);
1053
1054     final Window window = SwingUtilities.getWindowAncestor(this);
1055     if (window != null) {
1056       final ComponentAdapter componentAdapter = new ComponentAdapter() {
1057         @Override
1058         public void componentResized(final ComponentEvent e) {
1059           hidePopup();
1060         }
1061
1062         @Override
1063         public void componentMoved(final ComponentEvent e) {
1064           hidePopup();
1065         }
1066
1067         @Override
1068         public void componentShown(final ComponentEvent e) {
1069           hidePopup();
1070         }
1071
1072         @Override
1073         public void componentHidden(final ComponentEvent e) {
1074           hidePopup();
1075         }
1076       };
1077       window.addComponentListener(componentAdapter);
1078       Disposer.register(popupToolbar, new Disposable() {
1079         @Override
1080         public void dispose() {
1081           window.removeComponentListener(componentAdapter);
1082         }
1083       });
1084     }
1085   }
1086
1087
1088   private boolean isPopupShowing() {
1089     if (myPopup != null) {
1090       if (myPopup.getContent() != null) {
1091         return true;
1092       }
1093     }
1094     return false;
1095   }
1096
1097   private void hidePopup() {
1098     if (myPopup != null) {
1099       myPopup.cancel();
1100       processClosed();
1101     }
1102   }
1103
1104   private void processClosed() {
1105     if (myPopup == null) return;
1106
1107     Disposer.dispose(myPopup);
1108     myPopup = null;
1109
1110     myUpdater.updateActions(false, false);
1111   }
1112
1113   abstract static class PopupToolbar extends ActionToolbarImpl implements AnActionListener, Disposable {
1114     private final JComponent myParent;
1115
1116     public PopupToolbar(final String place,
1117                         final ActionGroup actionGroup,
1118                         final boolean horizontal,
1119                         final DataManager dataManager,
1120                         @NotNull ActionManagerEx actionManager,
1121                         final KeymapManagerEx keymapManager,
1122                         JComponent parent) {
1123       super(place, actionGroup, horizontal, false, dataManager, actionManager, keymapManager, true);
1124       myActionManager.addAnActionListener(this);
1125       myParent = parent;
1126     }
1127
1128     @Override
1129     public Container getParent() {
1130       Container parent = super.getParent();
1131       return parent != null ? parent : myParent;
1132     }
1133
1134     @Override
1135     public void dispose() {
1136       myActionManager.removeAnActionListener(this);
1137     }
1138
1139     @Override
1140     public void beforeActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
1141     }
1142
1143     @Override
1144     public void afterActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
1145       if (!myVisibleActions.contains(action)) {
1146         onOtherActionPerformed();
1147       }
1148     }
1149
1150     protected abstract void onOtherActionPerformed();
1151
1152     @Override
1153     public void beforeEditorTyping(final char c, final DataContext dataContext) {
1154     }
1155   }
1156
1157
1158   @Override
1159   public void setReservePlaceAutoPopupIcon(final boolean reserve) {
1160     myReservePlaceAutoPopupIcon = reserve;
1161   }
1162
1163   @Override
1164   public void setSecondaryActionsTooltip(String secondaryActionsTooltip) {
1165     mySecondaryActions.getTemplatePresentation().setDescription(secondaryActionsTooltip);
1166   }
1167
1168   @Override
1169   public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
1170     ArrayList<SwitchTarget> result = new ArrayList<SwitchTarget>();
1171
1172     if (getBounds().width * getBounds().height <= 0) return result;
1173
1174     for (int i = 0; i < getComponentCount(); i++) {
1175       Component each = getComponent(i);
1176       if (each instanceof ActionButton) {
1177         result.add(new ActionTarget((ActionButton)each));
1178       }
1179     }
1180     return result;
1181   }
1182
1183   private static class ActionTarget implements SwitchTarget {
1184     private final ActionButton myButton;
1185
1186     private ActionTarget(ActionButton button) {
1187       myButton = button;
1188     }
1189
1190     @Override
1191     public ActionCallback switchTo(boolean requestFocus) {
1192       myButton.click();
1193       return new ActionCallback.Done();
1194     }
1195
1196     @Override
1197     public boolean isVisible() {
1198       return myButton.isVisible();
1199     }
1200
1201     @Override
1202     public RelativeRectangle getRectangle() {
1203       return new RelativeRectangle(myButton.getParent(), myButton.getBounds());
1204     }
1205
1206     @Override
1207     public Component getComponent() {
1208       return myButton;
1209     }
1210
1211     @Override
1212     public String toString() {
1213       return myButton.getAction().toString();
1214     }
1215   }
1216
1217   @Override
1218   public SwitchTarget getCurrentTarget() {
1219     return null;
1220   }
1221
1222   @Override
1223   public boolean isCycleRoot() {
1224     return false;
1225   }
1226
1227   @Override
1228   public List<AnAction> getActions(boolean originalProvider) {
1229     ArrayList<AnAction> result = new ArrayList<AnAction>();
1230
1231     ArrayList<AnAction> secondary = new ArrayList<AnAction>();
1232     AnAction[] kids = myActionGroup.getChildren(null);
1233     for (AnAction each : kids) {
1234       if (myActionGroup.isPrimary(each)) {
1235         result.add(each);
1236       } else {
1237         secondary.add(each);
1238       }
1239     }
1240     result.add(new Separator());
1241     result.addAll(secondary);
1242
1243     return result;
1244   }
1245
1246   @Override
1247   public void setMiniMode(boolean minimalMode) {
1248     //if (myMinimalMode == minimalMode) return;
1249
1250     myMinimalMode = minimalMode;
1251     if (myMinimalMode) {
1252       setMinimumButtonSize(JBUI.emptySize());
1253       setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
1254       setBorder(new EmptyBorder(0, 0, 0, 0));
1255       setOpaque(false);
1256     } else {
1257       if (isInsideNavBar()) {
1258         setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
1259       }
1260       else {
1261         setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
1262       }
1263
1264       setMinimumButtonSize(myDecorateButtons ? new Dimension(30, 20) : DEFAULT_MINIMUM_BUTTON_SIZE);
1265       setOpaque(true);
1266       setLayoutPolicy(AUTO_LAYOUT_POLICY);
1267     }
1268
1269     myUpdater.updateActions(false, true);
1270   }
1271
1272   public void setAddSeparatorFirst(boolean addSeparatorFirst) {
1273     myAddSeparatorFirst = addSeparatorFirst;
1274     myUpdater.updateActions(false, true);
1275   }
1276 }