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