switcher - auto selection + fixes
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / actionSystem / impl / ActionToolbarImpl.java
1 /*
2  * Copyright 2000-2009 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.ide.DataManager;
19 import com.intellij.ide.impl.DataManagerImpl;
20 import com.intellij.openapi.Disposable;
21 import com.intellij.openapi.actionSystem.*;
22 import com.intellij.openapi.actionSystem.ex.ActionButtonLook;
23 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
24 import com.intellij.openapi.actionSystem.ex.AnActionListener;
25 import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
26 import com.intellij.openapi.application.Application;
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.Keymap;
31 import com.intellij.openapi.keymap.KeymapManagerListener;
32 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
33 import com.intellij.openapi.keymap.ex.WeakKeymapManagerListener;
34 import com.intellij.openapi.project.DumbAwareRunnable;
35 import com.intellij.openapi.ui.popup.*;
36 import com.intellij.openapi.util.ActionCallback;
37 import com.intellij.openapi.util.Computable;
38 import com.intellij.openapi.util.Disposer;
39 import com.intellij.openapi.util.IconLoader;
40 import com.intellij.openapi.wm.IdeFocusManager;
41 import com.intellij.ui.awt.RelativePoint;
42 import com.intellij.ui.awt.RelativeRectangle;
43 import com.intellij.ui.switcher.SwitchTarget;
44 import com.intellij.util.ui.UIUtil;
45 import com.intellij.util.ui.update.UiNotifyConnector;
46 import org.jetbrains.annotations.NotNull;
47
48 import javax.swing.*;
49 import java.awt.*;
50 import java.awt.event.ComponentAdapter;
51 import java.awt.event.ComponentEvent;
52 import java.awt.event.MouseEvent;
53 import java.util.*;
54 import java.util.List;
55
56 public class ActionToolbarImpl extends JPanel implements ActionToolbar {
57   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.impl.ActionToolbarImpl");
58
59   /**
60    * This array contains Rectangles which define bounds of the corresponding
61    * components in the toolbar. This list can be considerer as a cache of the
62    * Rectangle objects that are used in calculation of preferred sizes and
63    * layouting of components.
64    */
65   private final ArrayList<Rectangle> myComponentBounds = new ArrayList<Rectangle>();
66
67   private Dimension myMinimumButtonSize;
68   /**
69    * @see ActionToolbar#getLayoutPolicy()
70    */
71   private int myLayoutPolicy;
72   private int myOrientation;
73   private final ActionGroup myActionGroup;
74   private final String myPlace;
75   @SuppressWarnings({"FieldCanBeLocal"}) private final MyKeymapManagerListener myKeymapManagerListener;
76   @SuppressWarnings({"FieldCanBeLocal"}) private final MyTimerListener myTimerListener;
77   private ArrayList<AnAction> myNewVisibleActions;
78   protected ArrayList<AnAction> myVisibleActions;
79   private final PresentationFactory myPresentationFactory;
80   /**
81    * @see ActionToolbar#adjustTheSameSize(boolean)
82    */
83   private boolean myAdjustTheSameSize;
84
85   private final ActionButtonLook myButtonLook = null;
86   private final DataManager myDataManager;
87   protected final ActionManagerEx myActionManager;
88
89   private Rectangle myAutoPopupRec;
90
91   private static final Icon myAutoPopupIcon = IconLoader.getIcon("/ide/link.png");
92   private static final Icon mySecondaryGroupIcon = IconLoader.getIcon("/general/secondaryGroup.png");
93   private final DefaultActionGroup mySecondaryActions = new DefaultActionGroup();
94   private ActionButton mySecondaryActionsButton;
95
96   private final KeymapManagerEx myKeymapManager;
97   private int myFirstOusideIndex = -1;
98
99   private JBPopup myPopup;
100   private JComponent myTargetComponent;
101
102   private boolean myReservePlaceAutoPopupIcon = true;
103
104   public ActionToolbarImpl(final String place,
105                            final ActionGroup actionGroup,
106                            final boolean horizontal,
107                            DataManager dataManager,
108                            ActionManagerEx actionManager,
109                            KeymapManagerEx keymapManager) {
110     this(place, actionGroup, horizontal, dataManager, actionManager, keymapManager, false);
111   }
112
113   public ActionToolbarImpl(final String place,
114                            final ActionGroup actionGroup,
115                            final boolean horizontal,
116                            DataManager dataManager,
117                            ActionManagerEx actionManager,
118                            KeymapManagerEx keymapManager,
119                            boolean updateActionsNow) {
120     super(null);
121     myActionManager = actionManager;
122     myKeymapManager = keymapManager;
123     setMinimumButtonSize(DEFAULT_MINIMUM_BUTTON_SIZE);
124     setLayoutPolicy(AUTO_LAYOUT_POLICY);
125     setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
126     myPlace = place;
127     myActionGroup = actionGroup;
128     myPresentationFactory = new PresentationFactory();
129     myKeymapManagerListener = new MyKeymapManagerListener();
130     myTimerListener = new MyTimerListener();
131     myVisibleActions = new ArrayList<AnAction>();
132     myNewVisibleActions = new ArrayList<AnAction>();
133     myDataManager = dataManager;
134
135     setLayout(new BorderLayout());
136     setOrientation(horizontal ? SwingConstants.HORIZONTAL : SwingConstants.VERTICAL);
137
138     mySecondaryActions.getTemplatePresentation().setIcon(mySecondaryGroupIcon);
139     mySecondaryActions.setPopup(true);
140
141     updateActions(updateActionsNow);
142
143     //
144     keymapManager.addKeymapManagerListener(new WeakKeymapManagerListener(keymapManager, myKeymapManagerListener));
145     actionManager.addTimerListener(500, new WeakTimerListener(actionManager, myTimerListener));
146     // If the panel doesn't handle mouse event then it will be passed to its parent.
147     // It means that if the panel is in slidindg mode then the focus goes to the editor
148     // and panel will be automatically hidden.
149     enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
150   }
151
152   public JComponent getComponent() {
153     return this;
154   }
155
156   public int getLayoutPolicy() {
157     return myLayoutPolicy;
158   }
159
160   public void setLayoutPolicy(final int layoutPolicy) {
161     if (layoutPolicy != NOWRAP_LAYOUT_POLICY && layoutPolicy != WRAP_LAYOUT_POLICY && layoutPolicy != AUTO_LAYOUT_POLICY) {
162       throw new IllegalArgumentException("wrong layoutPolicy: " + layoutPolicy);
163     }
164     myLayoutPolicy = layoutPolicy;
165   }
166
167   protected void paintComponent(final Graphics g) {
168     super.paintComponent(g);
169
170     if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
171       if (myAutoPopupRec != null) {
172         if (myOrientation == SwingConstants.HORIZONTAL) {
173           final int dy = myAutoPopupRec.height / 2 - myAutoPopupIcon.getIconHeight() / 2;
174           myAutoPopupIcon.paintIcon(this, g, (int)myAutoPopupRec.getMaxX() - myAutoPopupIcon.getIconWidth() - 1, myAutoPopupRec.y + dy);
175         }
176         else {
177           final int dx = myAutoPopupRec.width / 2 - myAutoPopupIcon.getIconWidth() / 2;
178           myAutoPopupIcon.paintIcon(this, g, myAutoPopupRec.x + dx, (int)myAutoPopupRec.getMaxY() - myAutoPopupIcon.getIconWidth() - 1);
179         }
180       }
181     }
182   }
183
184   private void fillToolBar(final ArrayList<AnAction> actions, boolean layoutSecondaries) {
185     for (int i = 0; i < actions.size(); i++) {
186       final AnAction action = actions.get(i);
187
188       if (layoutSecondaries) {
189         if (!myActionGroup.isPrimary(action)) {
190           mySecondaryActions.add(action);
191           continue;
192         }
193       }
194
195       if (action instanceof Separator) {
196         if (i > 0 && i < actions.size() - 1) {
197           add(new MySeparator());
198         }
199       }
200       else if (action instanceof CustomComponentAction) {
201         add(((CustomComponentAction)action).createCustomComponent(myPresentationFactory.getPresentation(action)));
202       }
203       else {
204         final ActionButton button = createToolbarButton(action);
205         add(button);
206       }
207     }
208
209     if (mySecondaryActions.getChildrenCount() > 0) {
210       mySecondaryActionsButton = new SecondaryButton(mySecondaryActions, myPresentationFactory.getPresentation(mySecondaryActions), myPlace, DEFAULT_MINIMUM_BUTTON_SIZE);
211       mySecondaryActionsButton.setNoIconsInPopup(true);
212       add(mySecondaryActionsButton);
213     }
214   }
215
216   public ActionButton createToolbarButton(final AnAction action, final ActionButtonLook look, final String place, final Presentation presentation, final Dimension minimumSize) {
217     if (action.displayTextInToolbar()) {
218       return new ActionButtonWithText(action, presentation, place, minimumSize);
219     }
220
221     final ActionButton actionButton = new ActionButton(action, presentation, place, minimumSize) {
222       protected DataContext getDataContext() {
223         return getToolbarDataContext();
224       }
225     };
226     actionButton.setLook(look);
227     return actionButton;
228   }
229
230   private ActionButton createToolbarButton(final AnAction action) {
231     return createToolbarButton(action, myButtonLook, myPlace, myPresentationFactory.getPresentation(action), myMinimumButtonSize);
232   }
233
234   public void doLayout() {
235     if (!isValid()) {
236       calculateBounds(getSize(), myComponentBounds);
237     }
238     final int componentCount = getComponentCount();
239     LOG.assertTrue(componentCount <= myComponentBounds.size());
240     for (int i = componentCount - 1; i >= 0; i--) {
241       final Component component = getComponent(i);
242       component.setBounds(myComponentBounds.get(i));
243     }
244   }
245
246   public void validate() {
247     if (!isValid()) {
248       calculateBounds(getSize(), myComponentBounds);
249       super.validate();
250     }
251   }
252
253   /**
254    * @return maximum button width
255    */
256   private int getMaxButtonWidth() {
257     int width = 0;
258     for (int i = 0; i < getComponentCount(); i++) {
259       final Dimension dimension = getComponent(i).getPreferredSize();
260       width = Math.max(width, dimension.width);
261     }
262     return width;
263   }
264
265   /**
266    * @return maximum button height
267    */
268   public int getMaxButtonHeight() {
269     int height = 0;
270     for (int i = 0; i < getComponentCount(); i++) {
271       final Dimension dimension = getComponent(i).getPreferredSize();
272       height = Math.max(height, dimension.height);
273     }
274     return height;
275   }
276
277   private void calculateBoundsNowrapImpl(ArrayList<Rectangle> bounds) {
278     final int componentCount = getComponentCount();
279     LOG.assertTrue(componentCount <= bounds.size());
280
281     final int width = getWidth();
282     final int height = getHeight();
283
284     if (myAdjustTheSameSize) {
285       final int maxWidth = getMaxButtonWidth();
286       final int maxHeight = getMaxButtonHeight();
287
288       if (myOrientation == SwingConstants.HORIZONTAL) {
289         int xOffset = 0;
290         for (int i = 0; i < componentCount; i++) {
291           final Rectangle r = bounds.get(i);
292           r.setBounds(xOffset, (height - maxHeight) / 2, maxWidth, maxHeight);
293           xOffset += maxWidth;
294         }
295       }
296       else {
297         int yOffset = 0;
298         for (int i = 0; i < componentCount; i++) {
299           final Rectangle r = bounds.get(i);
300           r.setBounds((width - maxWidth) / 2, yOffset, maxWidth, maxHeight);
301           yOffset += maxHeight;
302         }
303       }
304     }
305     else {
306       if (myOrientation == SwingConstants.HORIZONTAL) {
307         final int maxHeight = getMaxButtonHeight();
308         int xOffset = 0;
309         final int yOffset = 0;
310         for (int i = 0; i < componentCount; i++) {
311           final Component component = getComponent(i);
312           final Dimension d = component.getPreferredSize();
313           final Rectangle r = bounds.get(i);
314           r.setBounds(xOffset, yOffset + (maxHeight - d.height) / 2, d.width, d.height);
315           xOffset += d.width;
316         }
317       }
318       else {
319         final int maxWidth = getMaxButtonWidth();
320         final int xOffset = 0;
321         int yOffset = 0;
322         for (int i = 0; i < componentCount; i++) {
323           final Component component = getComponent(i);
324           final Dimension d = component.getPreferredSize();
325           final Rectangle r = bounds.get(i);
326           r.setBounds(xOffset + (maxWidth - d.width) / 2, yOffset, d.width, d.height);
327           yOffset += d.height;
328         }
329       }
330     }
331   }
332
333   private void calculateBoundsAutoImp(Dimension sizeToFit, ArrayList<Rectangle> bounds) {
334     final int componentCount = getComponentCount();
335     LOG.assertTrue(componentCount <= bounds.size());
336
337     final boolean actualLayout = bounds == myComponentBounds;
338
339     if (actualLayout) {
340       myAutoPopupRec = null;
341     }
342                
343     int autoButtonSize = myAutoPopupIcon.getIconWidth();
344     boolean full = false;
345
346     if (myOrientation == SwingConstants.HORIZONTAL) {
347       int eachX = 0;
348       int eachY = 0;
349       for (int i = 0; i < componentCount; i++) {
350         final Component eachComp = getComponent(i);
351         final boolean isLast = i == componentCount - 1;
352
353         final Rectangle eachBound = new Rectangle(eachComp.getPreferredSize());
354         if (!full) {
355           boolean inside;
356           if (isLast) {
357             inside = eachX + eachBound.width <= sizeToFit.width;
358           } else {
359             inside = eachX + eachBound.width + autoButtonSize <= sizeToFit.width;
360           }
361
362           if (inside) {
363             if (eachComp == mySecondaryActionsButton) {
364               assert isLast;
365               if (sizeToFit.width != Integer.MAX_VALUE) {
366                 eachBound.x = sizeToFit.width - eachBound.width;
367                 eachX = (int)eachBound.getMaxX();
368               }
369               else {
370                 eachBound.x = eachX;
371               }
372             } else {
373               eachBound.x = eachX;
374               eachX += eachBound.width;
375             }
376             eachBound.y = eachY;
377           }
378           else {
379             full = true;
380           }
381         }
382
383         if (full) {
384           if (myAutoPopupRec == null) {
385             myAutoPopupRec = new Rectangle(eachX, eachY, sizeToFit.width - eachX - 1, sizeToFit.height - 1);
386             myFirstOusideIndex = i;
387           }
388           eachBound.x = Integer.MAX_VALUE;
389           eachBound.y = Integer.MAX_VALUE;
390         }
391
392         bounds.get(i).setBounds(eachBound);
393       }
394     }
395     else {
396       int eachX = 0;
397       int eachY = 0;
398       for (int i = 0; i < componentCount; i++) {
399         final Rectangle eachBound = new Rectangle(getComponent(i).getPreferredSize());
400         if (!full) {
401           boolean outside;
402           if (i < componentCount - 1) {
403             outside = eachY + eachBound.height + autoButtonSize < sizeToFit.height;
404           }
405           else {
406             outside = eachY + eachBound.height < sizeToFit.height;
407           }
408           if (outside) {
409             eachBound.x = eachX;
410             eachBound.y = eachY;
411             eachY += eachBound.height;
412           }
413           else {
414             full = true;
415           }
416         }
417
418         if (full) {
419           if (myAutoPopupRec == null) {
420             myAutoPopupRec = new Rectangle(eachX, eachY, sizeToFit.width - 1, sizeToFit.height - eachY - 1);
421             myFirstOusideIndex = i;
422           }
423           eachBound.x = Integer.MAX_VALUE;
424           eachBound.y = Integer.MAX_VALUE;
425         }
426
427         bounds.get(i).setBounds(eachBound);
428       }
429     }
430
431   }
432
433   private void calculateBoundsWrapImpl(Dimension sizeToFit, ArrayList<Rectangle> bounds) {
434     // We have to gracefull handle case when toolbar was not layed out yet.
435     // In this case we calculate bounds as it is a NOWRAP toolbar.
436     if (getWidth() == 0 || getHeight() == 0) {
437       try {
438         setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
439         calculateBoundsNowrapImpl(bounds);
440       }
441       finally {
442         setLayoutPolicy(WRAP_LAYOUT_POLICY);
443       }
444       return;
445     }
446
447
448     final int componentCount = getComponentCount();
449     LOG.assertTrue(componentCount <= bounds.size());
450
451     if (myAdjustTheSameSize) {
452       if (myOrientation == SwingConstants.HORIZONTAL) {
453         final int maxWidth = getMaxButtonWidth();
454         final int maxHeight = getMaxButtonHeight();
455
456         // Lay components out
457         int xOffset = 0;
458         int yOffset = 0;
459         // Calculate max size of a row. It's not possible to make more then 3 row toolbar
460         final int maxRowWidth = Math.max(sizeToFit.width, componentCount * maxWidth / 3);
461         for (int i = 0; i < componentCount; i++) {
462           if (xOffset + maxWidth > maxRowWidth) { // place component at new row
463             xOffset = 0;
464             yOffset += maxHeight;
465           }
466
467           final Rectangle each = bounds.get(i);
468           each.setBounds(xOffset, maxWidth, yOffset, maxHeight);
469
470           xOffset += maxWidth;
471         }
472       }
473       else {
474         final int maxWidth = getMaxButtonWidth();
475         final int maxHeight = getMaxButtonHeight();
476
477         // Lay components out
478         int xOffset = 0;
479         int yOffset = 0;
480         // Calculate max size of a row. It's not possible to make more then 3 column toolbar
481         final int maxRowHeight = Math.max(sizeToFit.height, componentCount * myMinimumButtonSize.height / 3);
482         for (int i = 0; i < componentCount; i++) {
483           if (yOffset + maxHeight > maxRowHeight) { // place component at new row
484             yOffset = 0;
485             xOffset += maxWidth;
486           }
487
488           final Rectangle each = bounds.get(i);
489           each.setBounds(xOffset, maxWidth, yOffset, maxHeight);
490
491           yOffset += maxHeight;
492         }
493       }
494     }
495     else {
496       if (myOrientation == SwingConstants.HORIZONTAL) {
497         // Calculate row height
498         int rowHeight = 0;
499         final Dimension[] dims = new Dimension[componentCount]; // we will use this dimesions later
500         for (int i = 0; i < componentCount; i++) {
501           dims[i] = getComponent(i).getPreferredSize();
502           final int height = dims[i].height;
503           rowHeight = Math.max(rowHeight, height);
504         }
505
506         // Lay components out
507         int xOffset = 0;
508         int yOffset = 0;
509         // Calculate max size of a row. It's not possible to make more then 3 row toolbar
510         final int maxRowWidth = Math.max(getWidth(), componentCount * myMinimumButtonSize.width / 3);
511         for (int i = 0; i < componentCount; i++) {
512           final Dimension d = dims[i];
513           if (xOffset + d.width > maxRowWidth) { // place component at new row
514             xOffset = 0;
515             yOffset += rowHeight;
516           }
517
518           final Rectangle each = bounds.get(i);
519           each.setBounds(xOffset, yOffset + (rowHeight - d.height) / 2, d.width, d.height);
520
521           xOffset += d.width;
522         }
523       }
524       else {
525         // Calculate row width
526         int rowWidth = 0;
527         final Dimension[] dims = new Dimension[componentCount]; // we will use this dimesions later
528         for (int i = 0; i < componentCount; i++) {
529           dims[i] = getComponent(i).getPreferredSize();
530           final int width = dims[i].width;
531           rowWidth = Math.max(rowWidth, width);
532         }
533
534         // Lay components out
535         int xOffset = 0;
536         int yOffset = 0;
537         // Calculate max size of a row. It's not possible to make more then 3 column toolbar
538         final int maxRowHeight = Math.max(getHeight(), componentCount * myMinimumButtonSize.height / 3);
539         for (int i = 0; i < componentCount; i++) {
540           final Dimension d = dims[i];
541           if (yOffset + d.height > maxRowHeight) { // place component at new row
542             yOffset = 0;
543             xOffset += rowWidth;
544           }
545
546           final Rectangle each = bounds.get(i);
547           each.setBounds(xOffset + (rowWidth - d.width) / 2, yOffset, d.width, d.height);
548
549           yOffset += d.height;
550         }
551       }
552     }
553   }
554
555   /**
556    * Calculates bounds of all the components in the toolbar
557    */
558   private void calculateBounds(Dimension size2Fit, ArrayList<Rectangle> bounds) {
559     bounds.clear();
560     for (int i = 0; i < getComponentCount(); i++) {
561       bounds.add(new Rectangle());
562     }
563
564     if (myLayoutPolicy == NOWRAP_LAYOUT_POLICY) {
565       calculateBoundsNowrapImpl(bounds);
566     }
567     else if (myLayoutPolicy == WRAP_LAYOUT_POLICY) {
568       calculateBoundsWrapImpl(size2Fit, bounds);
569     }
570     else if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
571       calculateBoundsAutoImp(size2Fit, bounds);
572     }
573     else {
574       throw new IllegalStateException("unknonw layoutPolicy: " + myLayoutPolicy);
575     }
576   }
577
578   public Dimension getPreferredSize() {
579     final ArrayList<Rectangle> bounds = new ArrayList<Rectangle>();
580     calculateBounds(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE), bounds);
581
582     int xLeft = Integer.MAX_VALUE;
583     int yTop = Integer.MAX_VALUE;
584     int xRight = Integer.MIN_VALUE;
585     int yBottom = Integer.MIN_VALUE;
586     for (int i = bounds.size() - 1; i >= 0; i--) {
587       final Rectangle each = bounds.get(i);
588       if (each.x == Integer.MAX_VALUE) continue;
589       xLeft = Math.min(xLeft, each.x);
590       yTop = Math.min(yTop, each.y);
591       xRight = Math.max(xRight, each.x + each.width);
592       yBottom = Math.max(yBottom, each.y + each.height);
593     }
594     final Dimension dimension = new Dimension(xRight - xLeft, yBottom - yTop);
595
596     if (myLayoutPolicy == AUTO_LAYOUT_POLICY && myReservePlaceAutoPopupIcon) {
597       if (myOrientation == SwingConstants.HORIZONTAL) {
598         dimension.width += myAutoPopupIcon.getIconWidth();
599       }
600       else {
601         dimension.height += myAutoPopupIcon.getIconHeight();
602       }
603     }
604
605     return dimension;
606   }
607
608   public Dimension getMinimumSize() {
609     if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
610       return new Dimension(myAutoPopupIcon.getIconWidth(), myMinimumButtonSize.height);
611     }
612     else {
613       return super.getMinimumSize();
614     }
615   }
616
617   private final class MySeparator extends JComponent {
618     private final Dimension mySize;
619
620     public MySeparator() {
621       if (myOrientation == SwingConstants.HORIZONTAL) {
622         mySize = new Dimension(6, 24);
623       }
624       else {
625         mySize = new Dimension(24, 6);
626       }
627     }
628
629     public Dimension getPreferredSize() {
630       return mySize;
631     }
632
633     protected void paintComponent(final Graphics g) {
634       g.setColor(UIUtil.getSeparatorShadow());
635       if (getParent() != null) {
636         if (myOrientation == SwingConstants.HORIZONTAL) {
637           UIUtil.drawLine(g, 3, 2, 3, getParent().getSize().height - 2);
638         }
639         else {
640           UIUtil.drawLine(g, 2, 3, getParent().getSize().width - 2, 3);
641         }
642       }
643     }
644   }
645
646   private final class MyKeymapManagerListener implements KeymapManagerListener {
647     public void activeKeymapChanged(final Keymap keymap) {
648       final int componentCount = getComponentCount();
649       for (int i = 0; i < componentCount; i++) {
650         final Component component = getComponent(i);
651         if (component instanceof ActionButton) {
652           ((ActionButton)component).updateToolTipText();
653         }
654       }
655     }
656   }
657
658   private final class MyTimerListener implements TimerListener {
659     public ModalityState getModalityState() {
660       return ModalityState.stateForComponent(ActionToolbarImpl.this);
661     }
662
663     public void run() {
664       if (!isShowing()) {
665         return;
666       }
667
668       // do not update when a popup menu is shown (if popup menu contains action which is also in the toolbar, it should not be enabled/disabled)
669       final MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
670       final MenuElement[] selectedPath = menuSelectionManager.getSelectedPath();
671       if (selectedPath.length > 0) {
672         return;
673       }
674
675       // don't update toolbar if there is currently active modal dialog
676
677       final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
678       if (window instanceof Dialog) {
679         final Dialog dialog = (Dialog)window;
680         if (dialog.isModal() && !SwingUtilities.isDescendingFrom(ActionToolbarImpl.this, dialog)) {
681           return;
682         }
683       }
684
685       updateActions(false);
686     }
687   }
688
689   public void adjustTheSameSize(final boolean value) {
690     if (myAdjustTheSameSize == value) {
691       return;
692     }
693     myAdjustTheSameSize = value;
694     revalidate();
695   }
696
697   public void setMinimumButtonSize(@NotNull final Dimension size) {
698     myMinimumButtonSize = size;
699     for (int i = getComponentCount() - 1; i >= 0; i--) {
700       final Component component = getComponent(i);
701       if (component instanceof ActionButton) {
702         final ActionButton button = (ActionButton)component;
703         button.setMinimumButtonSize(size);
704       }
705     }
706     revalidate();
707   }
708
709   public void setOrientation(final int orientation) {
710     if (SwingConstants.HORIZONTAL != orientation && SwingConstants.VERTICAL != orientation) {
711       throw new IllegalArgumentException("wrong orientation: " + orientation);
712     }
713     myOrientation = orientation;
714   }
715
716   public void updateActionsImmediately() {
717     ApplicationManager.getApplication().assertIsDispatchThread();
718     updateActions(true);
719   }
720
721   private void updateActions(boolean now) {
722     final Runnable updateRunnable = new Runnable() {
723       public void run() {
724         myNewVisibleActions.clear();
725         final DataContext dataContext = getDataContext();
726
727         Utils.expandActionGroup(myActionGroup, myNewVisibleActions, myPresentationFactory, dataContext, myPlace, myActionManager);
728
729         if (!myNewVisibleActions.equals(myVisibleActions)) {
730           // should rebuild UI
731
732           final boolean changeBarVisibility = myNewVisibleActions.isEmpty() || myVisibleActions.isEmpty();
733
734           final ArrayList<AnAction> temp = myVisibleActions;
735           myVisibleActions = myNewVisibleActions;
736           myNewVisibleActions = temp;
737
738           removeAll();
739           mySecondaryActions.removeAll();
740           mySecondaryActionsButton = null;
741           fillToolBar(myVisibleActions, getLayoutPolicy() == AUTO_LAYOUT_POLICY && myOrientation == SwingConstants.HORIZONTAL);
742
743           if (changeBarVisibility) {
744             revalidate();
745           }
746           else {
747             final Container parent = getParent();
748             if (parent != null) {
749               parent.invalidate();
750               parent.validate();
751             }
752           }
753           repaint();
754         }
755       }
756     };
757
758     if (now) {
759       updateRunnable.run();
760     } else {
761       final Application app = ApplicationManager.getApplication();
762       final IdeFocusManager fm = IdeFocusManager.getInstance(null);
763
764       if (!app.isUnitTestMode() && !app.isHeadlessEnvironment()) {
765         if (app.isDispatchThread()) {
766           fm.doWhenFocusSettlesDown(updateRunnable);
767         } else {
768           UiNotifyConnector.doWhenFirstShown(this, new Runnable() {
769             public void run() {
770               fm.doWhenFocusSettlesDown(updateRunnable);
771             }
772           });
773         }
774       }
775     }
776   }
777
778   public void setTargetComponent(final JComponent component) {
779     myTargetComponent = component;
780
781     if (myTargetComponent != null && myTargetComponent.isVisible()) {
782       ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
783         public void run() {
784           updateActions(false);
785         }
786       }, ModalityState.stateForComponent(myTargetComponent));
787     }
788   }
789
790   protected DataContext getToolbarDataContext() {
791     return getDataContext();
792   }
793
794   protected DataContext getDataContext() {
795     return myTargetComponent != null ? myDataManager.getDataContext(myTargetComponent) : ((DataManagerImpl)myDataManager).getDataContextTest(this);
796   }
797
798   protected void processMouseMotionEvent(final MouseEvent e) {
799     super.processMouseMotionEvent(e);
800
801     if (getLayoutPolicy() != AUTO_LAYOUT_POLICY) {
802       return;
803     }
804     if (myAutoPopupRec != null && myAutoPopupRec.contains(e.getPoint())) {
805       IdeFocusManager.getInstance(null).doWhenFocusSettlesDown(new Runnable() {
806         public void run() {
807           showAutoPopup();
808         }
809       });
810     }
811   }
812
813   private void showAutoPopup() {
814     if (isPopupShowing()) return;
815
816     final ActionGroup group;
817     if (myOrientation == SwingConstants.HORIZONTAL) {
818       group = myActionGroup;
819     }
820     else {
821       final DefaultActionGroup outside = new DefaultActionGroup();
822       for (int i = myFirstOusideIndex; i < myVisibleActions.size(); i++) {
823         outside.add(myVisibleActions.get(i));
824       }
825       group = outside;
826     }
827
828     PopupToolbar popupToolbar = new PopupToolbar(myPlace, group, true, myDataManager, myActionManager, myKeymapManager) {
829       protected void onOtherActionPerformed() {
830         hidePopup();
831       }
832
833       protected DataContext getDataContext() {
834         return ActionToolbarImpl.this.getDataContext();
835       }
836     };
837     popupToolbar.setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
838
839     Point location;
840     if (myOrientation == SwingConstants.HORIZONTAL) {
841       location = getLocationOnScreen();
842     }
843     else {
844       location = getLocationOnScreen();
845       location.y = location.y + getHeight() - popupToolbar.getPreferredSize().height;
846     }
847
848
849     final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(popupToolbar, null);
850     builder.setResizable(false)
851       .setRequestFocus(false)
852       .setTitle(null)
853       .setCancelOnClickOutside(true)
854       .setCancelOnOtherWindowOpen(true)
855       .setCancelCallback(new Computable<Boolean>() {
856         public Boolean compute() {
857           return myActionManager.isActionPopupStackEmpty();
858         }
859       })
860       .setCancelOnMouseOutCallback(new MouseChecker() {
861         public boolean check(final MouseEvent event) {
862           return myAutoPopupRec != null &&
863                  myActionManager.isActionPopupStackEmpty() &&
864                  !new RelativeRectangle(ActionToolbarImpl.this, myAutoPopupRec).contains(new RelativePoint(event));
865         }
866       });
867
868     builder.addListener(new JBPopupAdapter() {
869       public void onClosed(LightweightWindowEvent event) {
870         processClosed();
871       }
872     });
873     myPopup = builder.createPopup();
874
875     Disposer.register(myPopup, popupToolbar);
876
877     myPopup.showInScreenCoordinates(this, location);
878
879     final Window window = SwingUtilities.getWindowAncestor(this);
880     if (window != null) {
881       final ComponentAdapter componentAdapter = new ComponentAdapter() {
882         public void componentResized(final ComponentEvent e) {
883           hidePopup();
884         }
885
886         public void componentMoved(final ComponentEvent e) {
887           hidePopup();
888         }
889
890         public void componentShown(final ComponentEvent e) {
891           hidePopup();
892         }
893
894         public void componentHidden(final ComponentEvent e) {
895           hidePopup();
896         }
897       };
898       window.addComponentListener(componentAdapter);
899       Disposer.register(popupToolbar, new Disposable() {
900         public void dispose() {
901           window.removeComponentListener(componentAdapter);
902         }
903       });
904     }
905   }
906
907
908   private boolean isPopupShowing() {
909     if (myPopup != null) {
910       if (myPopup.getContent() != null) {
911         return true;
912       }
913     }
914     return false;
915   }
916
917   private void hidePopup() {
918     if (myPopup != null) {
919       myPopup.cancel();
920       processClosed();
921     }
922   }
923
924   private void processClosed() {
925     if (myPopup == null) return;
926
927     Disposer.dispose(myPopup);
928     myPopup = null;
929
930     updateActions(false);
931   }
932
933   abstract static class PopupToolbar extends ActionToolbarImpl implements AnActionListener, Disposable {
934     public PopupToolbar(final String place,
935                         final ActionGroup actionGroup,
936                         final boolean horizontal,
937                         final DataManager dataManager,
938                         final ActionManagerEx actionManager,
939                         final KeymapManagerEx keymapManager) {
940       super(place, actionGroup, horizontal, dataManager, actionManager, keymapManager, true);
941       myActionManager.addAnActionListener(this);
942     }
943
944     public void dispose() {
945       myActionManager.removeAnActionListener(this);
946     }
947
948     public void beforeActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
949     }
950
951     public void afterActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
952       if (!myVisibleActions.contains(action)) {
953         onOtherActionPerformed();
954       }
955     }
956
957     protected abstract void onOtherActionPerformed();
958
959     public void beforeEditorTyping(final char c, final DataContext dataContext) {
960     }
961   }
962
963
964   public void setReservePlaceAutoPopupIcon(final boolean reserve) {
965     myReservePlaceAutoPopupIcon = reserve;
966   }
967
968   public void setSecondaryActionsTooltip(String secondaryActionsTooltip) {
969     mySecondaryActions.getTemplatePresentation().setDescription(secondaryActionsTooltip);
970   }
971
972   private static class SecondaryButton extends ActionButton {
973     private SecondaryButton(AnAction action, Presentation presentation, String place, @NotNull Dimension minimumSize) {
974       super(action, presentation, place, minimumSize);
975     }
976
977     @Override
978     protected void paintButtonLook(Graphics g) {
979       final Color bright = new Color(255, 255, 255, 200);
980       final Color dark = new Color(64, 64, 64, 110);
981
982       int padding = 3;
983
984       g.setColor(bright);
985       g.drawLine(0, padding, 0, getHeight() - padding - 1);
986       g.setColor(dark);
987       g.drawLine(1, padding, 1, getHeight() - padding - 1);
988
989       super.paintButtonLook(g);
990     }
991   }
992
993   public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
994     ArrayList<SwitchTarget> result = new ArrayList<SwitchTarget>();
995
996     if ((getBounds().width * getBounds().height) <= 0) return result;
997
998     for (int i = 0; i < getComponentCount(); i++) {
999       Component each = getComponent(i);
1000       if (each instanceof ActionButton) {
1001         result.add(new ActionTarget((ActionButton)each));
1002       }
1003     }
1004     return result;
1005   }
1006
1007   private class ActionTarget implements SwitchTarget {
1008     private ActionButton myButton;
1009
1010     private ActionTarget(ActionButton button) {
1011       myButton = button;
1012     }
1013
1014     public ActionCallback switchTo(boolean requestFocus) {
1015       myButton.click();
1016       return new ActionCallback.Done();
1017     }
1018
1019     public boolean isVisible() {
1020       return myButton.isVisible();
1021     }
1022
1023     public RelativeRectangle getRectangle() {
1024       return new RelativeRectangle(myButton.getParent(), myButton.getBounds());
1025     }
1026
1027     public Component getComponent() {
1028       return myButton;
1029     }
1030
1031     @Override
1032     public String toString() {
1033       return myButton.getAction().toString();
1034     }
1035   }
1036
1037   public SwitchTarget getCurrentTarget() {
1038     return null;
1039   }
1040
1041   public boolean isCycleRoot() {
1042     return false;
1043   }
1044
1045   public List<AnAction> getActions(boolean originalProvider) {
1046     ArrayList<AnAction> result = new ArrayList<AnAction>();
1047
1048     ArrayList<AnAction> secondary = new ArrayList<AnAction>();
1049     if (myActionGroup != null) {
1050       AnAction[] kids = myActionGroup.getChildren(null);
1051       for (AnAction each : kids) {
1052         if (myActionGroup.isPrimary(each)) {
1053           result.add(each);
1054         } else {
1055           secondary.add(each);
1056         }
1057       }
1058     }
1059     result.add(new Separator());
1060     result.addAll(secondary);
1061
1062     return result;
1063   }
1064 }