blob: 9af1d30c28bf4e7e7a555dfa7fd4a1f8fd4a35c5 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.actionSystem.impl;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public class Utils{
private static final Logger LOG=Logger.getInstance("#com.intellij.openapi.actionSystem.impl.Utils");
@NonNls public static final String NOTHING_HERE = "Nothing here";
public static final AnAction EMPTY_MENU_FILLER = new AnAction(NOTHING_HERE) {
{
getTemplatePresentation().setEnabled(false);
}
@Override
public void actionPerformed(AnActionEvent e) {
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(false);
super.update(e);
}
};
private Utils() {}
private static void handleUpdateException(AnAction action, Presentation presentation, Throwable exc) {
String id = ActionManager.getInstance().getId(action);
if (id != null) {
LOG.error("update failed for AnAction with ID=" + id, exc);
}
else {
LOG.error("update failed for ActionGroup: " + action + "[" + presentation.getText() + "]", exc);
}
}
/**
* @param list this list contains expanded actions.
* @param actionManager manager
*/
public static void expandActionGroup(@NotNull ActionGroup group,
List<AnAction> list,
PresentationFactory presentationFactory,
@NotNull DataContext context,
String place,
ActionManager actionManager){
expandActionGroup(group, list, presentationFactory, context, place, actionManager, false, group instanceof CompactActionGroup);
}
public static void expandActionGroup(@NotNull ActionGroup group,
List<AnAction> list,
PresentationFactory presentationFactory,
DataContext context,
@NotNull String place,
ActionManager actionManager,
boolean transparentOnly) {
expandActionGroup(group, list, presentationFactory, context, place, actionManager, transparentOnly, false);
}
/**
* @param list this list contains expanded actions.
* @param actionManager manager
*/
public static void expandActionGroup(@NotNull ActionGroup group,
List<AnAction> list,
PresentationFactory presentationFactory,
DataContext context,
@NotNull String place,
ActionManager actionManager,
boolean transparentOnly,
boolean hideDisabled) {
Presentation presentation = presentationFactory.getPresentation(group);
AnActionEvent e = new AnActionEvent(
null,
context,
place,
presentation,
actionManager,
0
);
if (!doUpdate(group, e, presentation)) return;
if (!presentation.isVisible()) { // don't process invisible groups
return;
}
AnAction[] children = group.getChildren(e);
for (int i = 0; i < children.length; i++) {
AnAction child = children[i];
if (child == null) {
String groupId = ActionManager.getInstance().getId(group);
LOG.error("action is null: i=" + i + " group=" + group + " group id=" + groupId);
continue;
}
presentation = presentationFactory.getPresentation(child);
AnActionEvent e1 = new AnActionEvent(null, context, place, presentation, actionManager, 0);
e1.setInjectedContext(child.isInInjectedContext());
if (transparentOnly && child.isTransparentUpdate() || !transparentOnly) {
if (!doUpdate(child, e1, presentation)) continue;
}
if (!presentation.isVisible() || (!presentation.isEnabled() && hideDisabled)) { // don't create invisible items in the menu
continue;
}
if (child instanceof ActionGroup) {
ActionGroup actionGroup = (ActionGroup)child;
boolean skip = hideDisabled && !hasEnabledChildren(actionGroup, presentationFactory, context, place);
if (skip) {
continue;
}
if (actionGroup.isPopup()) { // popup menu has its own presentation
if (actionGroup.disableIfNoVisibleChildren()) {
final boolean visibleChildren = hasVisibleChildren(actionGroup, presentationFactory, context, place);
if (actionGroup.hideIfNoVisibleChildren() && !visibleChildren) {
continue;
}
presentation.setEnabled(actionGroup.canBePerformed(context) || visibleChildren);
}
list.add(child);
}
else {
expandActionGroup((ActionGroup)child, list, presentationFactory, context, place, actionManager, false, hideDisabled);
}
}
else if (child instanceof Separator) {
if (!StringUtil.isEmpty(((Separator)child).getText()) || (!list.isEmpty() && !(list.get(list.size() - 1) instanceof Separator))) {
list.add(child);
}
}
else {
if (hideDisabled && !hasEnabledChildren(new DefaultActionGroup(child), presentationFactory, context, place)) {
continue;
}
list.add(child);
}
}
}
// returns false if exception was thrown and handled
private static boolean doUpdate(final AnAction action, final AnActionEvent e, final Presentation presentation) throws ProcessCanceledException {
if (ApplicationManager.getApplication().isDisposed()) return false;
long startTime = System.currentTimeMillis();
final boolean result;
try {
result = !ActionUtil.performDumbAwareUpdate(action, e, false);
}
catch (ProcessCanceledException ex) {
throw ex;
}
catch (Throwable exc) {
handleUpdateException(action, presentation, exc);
return false;
}
long endTime = System.currentTimeMillis();
if (endTime - startTime > 10 && LOG.isDebugEnabled()) {
LOG.debug("Action " + action + ": updated in " + (endTime-startTime) + " ms");
}
return result;
}
private static boolean hasVisibleChildren(ActionGroup group, PresentationFactory factory, DataContext context, String place) {
return hasChildrenWithState(group, factory, context, place, true, false);
}
private static boolean hasEnabledChildren(ActionGroup group, PresentationFactory factory, DataContext context, String place) {
return hasChildrenWithState(group, factory, context, place, false, true);
}
private static boolean hasChildrenWithState(ActionGroup group,
PresentationFactory factory,
DataContext context,
String place,
boolean checkVisible,
boolean checkEnabled) {
//noinspection InstanceofIncompatibleInterface
if (group instanceof AlwaysVisibleActionGroup) {
return true;
}
AnActionEvent event = new AnActionEvent(null, context, place, factory.getPresentation(group), ActionManager.getInstance(), 0);
event.setInjectedContext(group.isInInjectedContext());
for (AnAction anAction : group.getChildren(event)) {
if (anAction == null) {
LOG.error("Null action found in group " + group + ", " + factory.getPresentation(group));
continue;
}
if (anAction instanceof Separator) {
continue;
}
final Project project = CommonDataKeys.PROJECT.getData(context);
if (project != null && DumbService.getInstance(project).isDumb() && !anAction.isDumbAware()) {
continue;
}
final Presentation presentation = factory.getPresentation(anAction);
updateGroupChild(context, place, anAction, presentation);
if (anAction instanceof ActionGroup) {
ActionGroup childGroup = (ActionGroup)anAction;
// popup menu must be visible itself
if (childGroup.isPopup()) {
if ((checkVisible && !presentation.isVisible()) || (checkEnabled && !presentation.isEnabled())) {
continue;
}
}
if (hasChildrenWithState(childGroup, factory, context, place, checkVisible, checkEnabled)) {
return true;
}
}
else if ((checkVisible && presentation.isVisible()) || (checkEnabled && presentation.isEnabled())) {
return true;
}
}
return false;
}
public static void updateGroupChild(DataContext context, String place, AnAction anAction, final Presentation presentation) {
AnActionEvent event1 = new AnActionEvent(null, context, place, presentation, ActionManager.getInstance(), 0);
event1.setInjectedContext(anAction.isInInjectedContext());
doUpdate(anAction, event1, presentation);
}
public static void fillMenu(@NotNull final ActionGroup group,
final JComponent component,
final boolean enableMnemonics,
final PresentationFactory presentationFactory,
@NotNull DataContext context,
final String place,
final boolean isWindowMenu,
final boolean mayDataContextBeInvalid){
final ActionCallback menuBuilt = new ActionCallback();
final boolean checked = group instanceof CheckedActionGroup;
final ArrayList<AnAction> list = new ArrayList<AnAction>();
expandActionGroup(group, list, presentationFactory, context, place, ActionManager.getInstance());
final boolean fixMacScreenMenu = SystemInfo.isMacSystemMenu && isWindowMenu && Registry.is("actionSystem.mac.screenMenuNotUpdatedFix");
final ArrayList<Component> children = new ArrayList<Component>();
for (int i = 0, size = list.size(); i < size; i++) {
final AnAction action = list.get(i);
if (action instanceof Separator) {
final String text = ((Separator)action).getText();
if (!StringUtil.isEmpty(text) || (i > 0 && i < size - 1)) {
component.add(new JPopupMenu.Separator() {
private final JMenuItem myMenu = !StringUtil.isEmpty(text) ? new JMenuItem(text) : null;
@Override
public Insets getInsets() {
final Insets insets = super.getInsets();
final boolean fix = UIUtil.isUnderGTKLookAndFeel() &&
getBorder() != null &&
insets.top + insets.bottom == 0;
return fix ? new Insets(2, insets.left, 3, insets.right) : insets; // workaround for Sun bug #6636964
}
@Override
public void doLayout() {
super.doLayout();
if (myMenu != null) {
myMenu.setBounds(getBounds());
}
}
@Override
protected void paintComponent(Graphics g) {
if (UIUtil.isUnderWindowsClassicLookAndFeel() || UIUtil.isUnderDarcula() || UIUtil.isUnderWindowsLookAndFeel()) {
g.setColor(component.getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
if (myMenu != null) {
myMenu.paint(g);
} else {
super.paintComponent(g);
}
}
@Override
public Dimension getPreferredSize() {
return myMenu != null ? myMenu.getPreferredSize() : super.getPreferredSize();
}
});
}
}
else if (action instanceof ActionGroup &&
!(((ActionGroup)action).canBePerformed(context) &&
!hasVisibleChildren((ActionGroup)action, presentationFactory, context, place))) {
ActionMenu menu = new ActionMenu(context, place, (ActionGroup)action, presentationFactory, enableMnemonics, false);
component.add(menu);
children.add(menu);
}
else {
final ActionMenuItem each =
new ActionMenuItem(action, presentationFactory.getPresentation(action), place, context, enableMnemonics, !fixMacScreenMenu, checked);
component.add(each);
children.add(each);
}
}
if (list.isEmpty()) {
final ActionMenuItem each =
new ActionMenuItem(EMPTY_MENU_FILLER, presentationFactory.getPresentation(EMPTY_MENU_FILLER), place, context, enableMnemonics,
!fixMacScreenMenu, checked);
component.add(each);
children.add(each);
}
if (fixMacScreenMenu) {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (Component each : children) {
if (each.getParent() != null && each instanceof ActionMenuItem) {
((ActionMenuItem)each).prepare();
}
}
menuBuilt.setDone();
}
});
}
else {
menuBuilt.setDone();
}
menuBuilt.doWhenDone(new Runnable() {
public void run() {
if (!mayDataContextBeInvalid) return;
if (IdeFocusManager.getInstance(null).isFocusBeingTransferred()) {
IdeFocusManager.getInstance(null).doWhenFocusSettlesDown(new Runnable() {
public void run() {
if (!component.isShowing()) return;
DataContext context = DataManager.getInstance().getDataContext();
expandActionGroup(group, new ArrayList<AnAction>(), presentationFactory, context, place, ActionManager.getInstance());
for (Component each : children) {
if (each instanceof ActionMenuItem) {
((ActionMenuItem)each).updateContext(context);
} else if (each instanceof ActionMenu) {
((ActionMenu)each).updateContext(context);
}
}
}
});
}
}
});
}
}