blob: ffda2495ca9359d8b9b3693ed4a4f1445d4d57bb [file] [log] [blame]
/*
* Copyright 2000-2012 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.ide.ui.UISettings;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.ui.JBPopupMenu;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.ui.ScreenUtil;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
final class ActionPopupMenuImpl implements ActionPopupMenu, ApplicationActivationListener {
private final MyMenu myMenu;
private final ActionManagerImpl myManager;
private MessageBusConnection myConnection;
private final Application myApp;
private IdeFrame myFrame;
@Nullable private Getter<DataContext> myDataContextProvider;
public ActionPopupMenuImpl(String place, @NotNull ActionGroup group, ActionManagerImpl actionManager, @Nullable PresentationFactory factory) {
myManager = actionManager;
myMenu = new MyMenu(place, group, factory);
myApp = ApplicationManager.getApplication();
}
public JPopupMenu getComponent() {
return myMenu;
}
public void setDataContextProvider(@Nullable Getter<DataContext> dataContextProvider) {
myDataContextProvider = dataContextProvider;
}
private class MyMenu extends JBPopupMenu {
private final String myPlace;
private final ActionGroup myGroup;
private DataContext myContext;
private final PresentationFactory myPresentationFactory;
public MyMenu(String place, @NotNull ActionGroup group, @Nullable PresentationFactory factory) {
myPlace = place;
myGroup = group;
myPresentationFactory = factory != null ? factory : new MenuItemPresentationFactory();
addPopupMenuListener(new MyPopupMenuListener());
}
public void show(final Component component, int x, int y) {
if (!component.isShowing()) {
//noinspection HardCodedStringLiteral
throw new IllegalArgumentException("component must be shown on the screen");
}
removeAll();
// Fill menu. Only after filling menu has non zero size.
int x2 = Math.max(0, Math.min(x, component.getWidth() - 1)); // fit x into [0, width-1]
int y2 = Math.max(0, Math.min(y, component.getHeight() - 1)); // fit y into [0, height-1]
myContext = myDataContextProvider != null ? myDataContextProvider.get() : DataManager.getInstance().getDataContext(component, x2, y2);
Utils.fillMenu(myGroup, this, true, myPresentationFactory, myContext, myPlace, false, false);
if (getComponentCount() == 0) {
return;
}
Dimension preferredSize = getPreferredSize();
// Translate (x,y) into screen coordinate syetem
int _x, _y; // these are screen coordinates of clicked point
Point p = component.getLocationOnScreen();
_x = p.x + x;
_y = p.y + y;
// Determine graphics device which contains our point
GraphicsConfiguration targetGraphicsConfiguration = null;
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = env.getScreenDevices();
for (GraphicsDevice device : devices) {
GraphicsConfiguration graphicsConfiguration = device.getDefaultConfiguration();
Rectangle r = graphicsConfiguration.getBounds();
if (r.x <= _x && _x <= r.x + r.width && r.y <= _y && _y <= r.y + r.height) {
targetGraphicsConfiguration = graphicsConfiguration;
break;
}
}
if (targetGraphicsConfiguration == null && devices.length > 0) {
targetGraphicsConfiguration = env.getDefaultScreenDevice().getDefaultConfiguration();
}
if (targetGraphicsConfiguration == null) {
//noinspection HardCodedStringLiteral
throw new IllegalStateException("It's impossible to determine target graphics environment for point (" + _x + "," + _y + ")");
}
// Determine real client area of target graphics configuration
Insets insets = ScreenUtil.getScreenInsets(targetGraphicsConfiguration);
Rectangle targetRectangle = targetGraphicsConfiguration.getBounds();
targetRectangle.x += insets.left;
targetRectangle.y += insets.top;
targetRectangle.width -= insets.left + insets.right;
targetRectangle.height -= insets.top + insets.bottom;
// Fit popup into targetRectangle.
// The algorithm is the following:
// First of all try to move menu up on its height. If menu's left-top corner
// is inside screen bounds after that, then OK. Otherwise, if menu is too high
// (left-top corner is outside of screen bounds) then try to move menu up on
// not visible visible area height.
if (_x + preferredSize.width > targetRectangle.x + targetRectangle.width) {
x -= preferredSize.width;
}
if (_y + preferredSize.height > targetRectangle.y + targetRectangle.height) {
int invisibleHeight = _y + preferredSize.height - targetRectangle.y - targetRectangle.height;
y -= invisibleHeight;
}
if (myApp != null) {
if (myApp.isActive()) {
Component frame = UIUtil.findUltimateParent(component);
if (frame instanceof IdeFrame) {
myFrame = (IdeFrame)frame;
}
myConnection = myApp.getMessageBus().connect();
myConnection.subscribe(ApplicationActivationListener.TOPIC, ActionPopupMenuImpl.this);
}
}
super.show(component, x, y);
}
@Override
public void setVisible(boolean b) {
super.setVisible(b);
if (!b) ReflectionUtil.resetField(this, "invoker");
}
private class MyPopupMenuListener implements PopupMenuListener {
public void popupMenuCanceled(PopupMenuEvent e) {
disposeMenu();
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
disposeMenu();
}
private void disposeMenu() {
myManager.removeActionPopup(ActionPopupMenuImpl.this);
MyMenu.this.removeAll();
if (myConnection != null) {
myConnection.disconnect();
}
}
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
MyMenu.this.removeAll();
Utils.fillMenu(myGroup, MyMenu.this, !UISettings.getInstance().DISABLE_MNEMONICS, myPresentationFactory, myContext, myPlace, false,
false);
myManager.addActionPopup(ActionPopupMenuImpl.this);
}
}
}
@Override
public void applicationActivated(IdeFrame ideFrame) {
}
@Override
public void applicationDeactivated(IdeFrame ideFrame) {
if (myFrame == ideFrame) {
myMenu.setVisible(false);
}
}
}