85c0555f9bc6756c328eed4263eed7a516ab6815
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / actionSystem / impl / Utils.java
1 /*
2  * Copyright 2000-2013 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.openapi.actionSystem.*;
20 import com.intellij.openapi.actionSystem.ex.ActionUtil;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.progress.ProcessCanceledException;
24 import com.intellij.openapi.project.DumbService;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.ActionCallback;
27 import com.intellij.openapi.util.SystemInfo;
28 import com.intellij.openapi.util.registry.Registry;
29 import com.intellij.openapi.wm.IdeFocusManager;
30 import com.intellij.util.ui.UIUtil;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.NotNull;
33
34 import javax.swing.*;
35 import java.awt.*;
36 import java.util.ArrayList;
37 import java.util.List;
38
39 /**
40  * @author Anton Katilin
41  * @author Vladimir Kondratyev
42  */
43 public class Utils{
44   private static final Logger LOG=Logger.getInstance("#com.intellij.openapi.actionSystem.impl.Utils");
45   @NonNls public static final String NOTHING_HERE = "Nothing here";
46   public static final AnAction EMPTY_MENU_FILLER = new AnAction(NOTHING_HERE) {
47
48     {
49       getTemplatePresentation().setEnabled(false);
50     }
51
52     @Override
53     public void actionPerformed(AnActionEvent e) {
54     }
55
56     @Override
57     public void update(AnActionEvent e) {
58       e.getPresentation().setEnabled(false);
59       super.update(e);
60     }
61   };
62
63   private Utils() {}
64
65   private static void handleUpdateException(AnAction action, Presentation presentation, Throwable exc) {
66     String id = ActionManager.getInstance().getId(action);
67     if (id != null) {
68       LOG.error("update failed for AnAction with ID=" + id, exc);
69     }
70     else {
71       LOG.error("update failed for ActionGroup: " + action + "[" + presentation.getText() + "]", exc);
72     }
73   }
74
75   /**
76    * @param list this list contains expanded actions.
77    * @param actionManager manager
78    */
79   public static void expandActionGroup(@NotNull ActionGroup group,
80                                        List<AnAction> list,
81                                        PresentationFactory presentationFactory,
82                                        @NotNull DataContext context,
83                                        String place,
84                                        ActionManager actionManager){
85     expandActionGroup(group, list, presentationFactory, context, place, actionManager, false, group instanceof CompactActionGroup);
86   }
87
88   public static void expandActionGroup(@NotNull ActionGroup group,
89                                        List<AnAction> list,
90                                        PresentationFactory presentationFactory,
91                                        DataContext context,
92                                        @NotNull String place,
93                                        ActionManager actionManager,
94                                        boolean transparentOnly) {
95     expandActionGroup(group, list, presentationFactory, context, place, actionManager, transparentOnly, false);
96   }
97
98   /**
99    * @param list this list contains expanded actions.
100    * @param actionManager manager
101    */
102   public static void expandActionGroup(@NotNull ActionGroup group,
103                                        List<AnAction> list,
104                                        PresentationFactory presentationFactory,
105                                        DataContext context,
106                                        @NotNull String place,
107                                        ActionManager actionManager,
108                                        boolean transparentOnly,
109                                        boolean hideDisabled) {
110     Presentation presentation = presentationFactory.getPresentation(group);
111     AnActionEvent e = new AnActionEvent(
112       null,
113       context,
114       place,
115       presentation,
116       actionManager,
117       0
118     );
119     if (!doUpdate(group, e, presentation)) return;
120
121     if (!presentation.isVisible()) { // don't process invisible groups
122       return;
123     }
124     AnAction[] children = group.getChildren(e);
125     for (int i = 0; i < children.length; i++) {
126       AnAction child = children[i];
127       if (child == null) {
128         String groupId = ActionManager.getInstance().getId(group);
129         LOG.error("action is null: i=" + i + " group=" + group + " group id=" + groupId);
130         continue;
131       }
132
133       presentation = presentationFactory.getPresentation(child);
134       AnActionEvent e1 = new AnActionEvent(null, context, place, presentation, actionManager, 0);
135       e1.setInjectedContext(child.isInInjectedContext());
136
137       if (transparentOnly && child.isTransparentUpdate() || !transparentOnly) {
138         if (!doUpdate(child, e1, presentation)) continue;
139       }
140
141       if (!presentation.isVisible() || (!presentation.isEnabled() && hideDisabled)) { // don't create invisible items in the menu
142         continue;
143       }
144       if (child instanceof ActionGroup) {
145         ActionGroup actionGroup = (ActionGroup)child;
146         boolean skip = hideDisabled && !hasEnabledChildren(actionGroup, presentationFactory, context, place);
147         if (skip) {
148           continue;
149         }
150         if (actionGroup.isPopup()) { // popup menu has its own presentation
151           if (actionGroup.disableIfNoVisibleChildren()) {
152             final boolean visibleChildren = hasVisibleChildren(actionGroup, presentationFactory, context, place);
153             if (actionGroup.hideIfNoVisibleChildren() && !visibleChildren) {
154               continue;
155             }
156             presentation.setEnabled(actionGroup.canBePerformed(context) || visibleChildren);
157           }
158
159
160           list.add(child);
161         }
162         else {
163           expandActionGroup((ActionGroup)child, list, presentationFactory, context, place, actionManager, false, hideDisabled);
164         }
165       }
166       else if (child instanceof Separator) {
167         if (!list.isEmpty() && !(list.get(list.size() - 1) instanceof Separator)) {
168           list.add(child);
169         }
170       }
171       else {
172         if (hideDisabled && !hasEnabledChildren(new DefaultActionGroup(child), presentationFactory, context, place)) {
173           continue;
174         }
175         list.add(child);
176       }
177     }
178   }
179
180   // returns false if exception was thrown and handled
181   private static boolean doUpdate(final AnAction action, final AnActionEvent e, final Presentation presentation) throws ProcessCanceledException {
182     if (ApplicationManager.getApplication().isDisposed()) return false;
183
184     long startTime = System.currentTimeMillis();
185     final boolean result;
186     try {
187       result = !ActionUtil.performDumbAwareUpdate(action, e, false);
188     }
189     catch (ProcessCanceledException ex) {
190       throw ex;
191     }
192     catch (Throwable exc) {
193       handleUpdateException(action, presentation, exc);
194       return false;
195     }
196     long endTime = System.currentTimeMillis();
197     if (endTime - startTime > 10 && LOG.isDebugEnabled()) {
198       LOG.debug("Action " + action + ": updated in " + (endTime-startTime) + " ms");
199     }
200     return result;
201   }
202
203   private static boolean hasVisibleChildren(ActionGroup group, PresentationFactory factory, DataContext context, String place) {
204     return hasChildrenWithState(group, factory, context, place, true, false);
205   }
206
207   private static boolean hasEnabledChildren(ActionGroup group, PresentationFactory factory, DataContext context, String place) {
208     return hasChildrenWithState(group, factory, context, place, false, true);
209   }
210
211   private static boolean hasChildrenWithState(ActionGroup group,
212                                               PresentationFactory factory,
213                                               DataContext context,
214                                               String place,
215                                               boolean checkVisible,
216                                               boolean checkEnabled) {
217     //noinspection InstanceofIncompatibleInterface
218     if (group instanceof AlwaysVisibleActionGroup) {
219       return true;
220     }
221
222     AnActionEvent event = new AnActionEvent(null, context, place, factory.getPresentation(group), ActionManager.getInstance(), 0);
223     event.setInjectedContext(group.isInInjectedContext());
224     for (AnAction anAction : group.getChildren(event)) {
225       if (anAction == null) {
226         LOG.error("Null action found in group " + group + ", " + factory.getPresentation(group));
227         continue;
228       }
229       if (anAction instanceof Separator) {
230         continue;
231       }
232       final Project project = CommonDataKeys.PROJECT.getData(context);
233       if (project != null && DumbService.getInstance(project).isDumb() && !anAction.isDumbAware()) {
234         continue;
235       }
236
237       final Presentation presentation = factory.getPresentation(anAction);
238       updateGroupChild(context, place, anAction, presentation);
239       if (anAction instanceof ActionGroup) {
240         ActionGroup childGroup = (ActionGroup)anAction;
241
242         // popup menu must be visible itself
243         if (childGroup.isPopup()) {
244           if ((checkVisible && !presentation.isVisible()) || (checkEnabled && !presentation.isEnabled())) {
245             continue;
246           }
247         }
248
249         if (hasChildrenWithState(childGroup, factory, context, place, checkVisible, checkEnabled)) {
250           return true;
251         }
252       }
253       else if ((checkVisible && presentation.isVisible()) || (checkEnabled && presentation.isEnabled())) {
254         return true;
255       }
256     }
257
258     return false;
259   }
260
261   public static void updateGroupChild(DataContext context, String place, AnAction anAction, final Presentation presentation) {
262     AnActionEvent event1 = new AnActionEvent(null, context, place, presentation, ActionManager.getInstance(), 0);
263     event1.setInjectedContext(anAction.isInInjectedContext());
264     doUpdate(anAction, event1, presentation);
265   }
266
267   public static void fillMenu(@NotNull final ActionGroup group,
268                               final JComponent component,
269                               final boolean enableMnemonics,
270                               final PresentationFactory presentationFactory,
271                               @NotNull DataContext context,
272                               final String place,
273                               final boolean isWindowMenu,
274                               final boolean mayDataContextBeInvalid){
275     final ActionCallback menuBuilt = new ActionCallback();
276     final boolean checked = group instanceof CheckedActionGroup;
277
278     final ArrayList<AnAction> list = new ArrayList<AnAction>();
279     expandActionGroup(group, list, presentationFactory, context, place, ActionManager.getInstance());
280
281     final boolean fixMacScreenMenu = SystemInfo.isMacSystemMenu && isWindowMenu && Registry.is("actionSystem.mac.screenMenuNotUpdatedFix");
282     final ArrayList<Component> children = new ArrayList<Component>();
283
284     for (int i = 0, size = list.size(); i < size; i++) {
285       final AnAction action = list.get(i);
286       if (action instanceof Separator) {
287         if (i > 0 && i < size - 1) {
288           component.add(new JPopupMenu.Separator() {
289             @Override
290             public Insets getInsets() {
291               final Insets insets = super.getInsets();
292               final boolean fix = UIUtil.isUnderGTKLookAndFeel() &&
293                                   getBorder() != null &&
294                                   insets.top + insets.bottom == 0;
295               return fix ? new Insets(2, insets.left, 3, insets.right) : insets;  // workaround for Sun bug #6636964
296             }
297
298             @Override
299             protected void paintComponent(Graphics g) {
300               if (UIUtil.isUnderWindowsClassicLookAndFeel() || UIUtil.isUnderDarcula() || UIUtil.isUnderWindowsLookAndFeel()) {
301                 g.setColor(component.getBackground());
302                 g.fillRect(0, 0, getWidth(), getHeight());
303               }
304               super.paintComponent(g);
305             }
306           });
307         }
308       }
309       else if (action instanceof ActionGroup &&
310                !(((ActionGroup)action).canBePerformed(context) &&
311                  !hasVisibleChildren((ActionGroup)action, presentationFactory, context, place))) {
312         ActionMenu menu = new ActionMenu(context, place, (ActionGroup)action, presentationFactory, enableMnemonics, false);
313         component.add(menu);
314         children.add(menu);
315       }
316       else {
317         final ActionMenuItem each =
318           new ActionMenuItem(action, presentationFactory.getPresentation(action), place, context, enableMnemonics, !fixMacScreenMenu, checked);
319         component.add(each);
320         children.add(each);
321       }
322     }
323
324     if (list.isEmpty()) {
325       final ActionMenuItem each =
326         new ActionMenuItem(EMPTY_MENU_FILLER, presentationFactory.getPresentation(EMPTY_MENU_FILLER), place, context, enableMnemonics,
327                            !fixMacScreenMenu, checked);
328       component.add(each);
329       children.add(each);
330     }
331
332     if (fixMacScreenMenu) {
333       //noinspection SSBasedInspection
334       SwingUtilities.invokeLater(new Runnable() {
335         public void run() {
336           for (Component each : children) {
337             if (each.getParent() != null && each instanceof ActionMenuItem) {
338               ((ActionMenuItem)each).prepare();
339             }
340           }
341           menuBuilt.setDone();
342         }
343       });
344     }
345     else {
346       menuBuilt.setDone();
347     }
348
349     menuBuilt.doWhenDone(new Runnable() {
350       public void run() {
351         if (!mayDataContextBeInvalid) return;
352
353         if (IdeFocusManager.getInstance(null).isFocusBeingTransferred()) {
354           IdeFocusManager.getInstance(null).doWhenFocusSettlesDown(new Runnable() {
355             public void run() {
356               if (!component.isShowing()) return;
357
358               DataContext context = DataManager.getInstance().getDataContext();
359               expandActionGroup(group, new ArrayList<AnAction>(), presentationFactory, context, place, ActionManager.getInstance());
360
361               for (Component each : children) {
362                 if (each instanceof ActionMenuItem) {
363                   ((ActionMenuItem)each).updateContext(context);
364                 } else if (each instanceof ActionMenu) {
365                   ((ActionMenu)each).updateContext(context);
366                 }
367               }
368             }
369           });
370         }
371       }
372     });
373
374   }
375 }