blob: 81d09d7886f841e914f24697f405d77510963c00 [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.ui.popup;
import com.intellij.codeInsight.lookup.LookupAdapter;
import com.intellij.codeInsight.lookup.LookupEvent;
import com.intellij.codeInsight.lookup.LookupEx;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.util.DimensionService;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBList;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* @author pegov
* @author Konstantin Bulenkov
*/
public class PopupPositionManager {
private PopupPositionManager() {
}
public enum Position {
TOP, BOTTOM, LEFT, RIGHT
}
public static void positionPopupInBestPosition(final JBPopup hint,
@Nullable final Editor editor,
@Nullable DataContext dataContext) {
final LookupEx lookup = LookupManager.getActiveLookup(editor);
if (lookup != null && lookup.getCurrentItem() != null && lookup.getComponent().isShowing()) {
new PositionAdjuster(lookup.getComponent()).adjust(hint);
lookup.addLookupListener(new LookupAdapter() {
@Override
public void lookupCanceled(LookupEvent event) {
if (hint.isVisible()) {
hint.cancel();
}
}
});
return;
}
final PositionAdjuster positionAdjuster = createPositionAdjuster();
if (positionAdjuster != null) {
positionAdjuster.adjust(hint);
return;
}
if (editor != null && editor.getComponent().isShowing()) {
hint.showInBestPositionFor(editor);
return;
}
if (dataContext != null) {
hint.showInBestPositionFor(dataContext);
}
}
private static Component discoverPopup(final DataKey<JBPopup> datakey, Component focusOwner) {
if (focusOwner == null) {
focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
}
if (focusOwner == null) return null;
final DataContext dataContext = DataManager.getInstance().getDataContext(focusOwner);
if (dataContext == null) return null;
final JBPopup popup = datakey.getData(dataContext);
if (popup != null && popup.isVisible()) {
return popup.getContent();
}
return null;
}
@Nullable
private static PositionAdjuster createPositionAdjuster() {
final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner == null) return null;
if (focusOwner instanceof JBList) {
return new PositionAdjuster(SwingUtilities.getWindowAncestor(focusOwner));
}
final Component existing = discoverPopup(LangDataKeys.POSITION_ADJUSTER_POPUP, focusOwner);
if (existing != null) {
return new PositionAdjuster2(existing, discoverPopup(LangDataKeys.PARENT_POPUP, focusOwner));
}
//final Window window = SwingUtilities.getWindowAncestor(focusOwner);
//return window == null ? null : new PositionAdjuster(window);
return null;
}
private static class PositionAdjuster2 extends PositionAdjuster {
private final Component myTopComponent;
private PositionAdjuster2(final Component relativeTo, final Component topComponent) {
super(relativeTo);
myTopComponent = topComponent == null ? relativeTo : topComponent;
}
@Override
protected int getYForTopPositioning() {
return myTopComponent.getLocationOnScreen().y;
}
}
public static class PositionAdjuster {
private final int myGap;
private final Component myRelativeTo;
private final Point myRelativeOnScreen;
private final Rectangle myScreenRect;
public PositionAdjuster(final Component relativeTo, int gap) {
myRelativeTo = relativeTo;
myRelativeOnScreen = relativeTo.getLocationOnScreen();
myScreenRect = ScreenUtil.getScreenRectangle(myRelativeOnScreen);
myGap = gap;
}
public PositionAdjuster(final Component relativeTo) {
this(relativeTo, 5);
}
protected Rectangle positionRight(final Dimension d) {
return new Rectangle(myRelativeOnScreen.x + myRelativeTo.getWidth() + myGap, myRelativeOnScreen.y, d.width,
d.height);
}
protected Rectangle positionLeft(final Dimension d) {
return new Rectangle(myRelativeOnScreen.x - myGap - d.width, myRelativeOnScreen.y, d.width, d.height);
}
protected Rectangle positionAbove(final Dimension d) {
return new Rectangle(myRelativeOnScreen.x, getYForTopPositioning() - myGap - d.height, d.width, d.height);
}
protected Rectangle positionUnder(final Dimension d) {
return new Rectangle(myRelativeOnScreen.x, myRelativeOnScreen.y + myGap + myRelativeTo.getHeight(), d.width, d.height);
}
protected int getYForTopPositioning() {
return myRelativeOnScreen.y;
}
/**
* Try to position:
* 1. to the right
* 2. to the left
* 3. above
* 4. under
*
* @param popup
*/
public void adjust(final JBPopup popup) {
adjust(popup, Position.RIGHT, Position.LEFT, Position.TOP, Position.BOTTOM);
}
public void adjust(final JBPopup popup, Position... traversalPolicy) {
final Dimension d = getPopupSize(popup);
Rectangle popupRect = null;
Rectangle r = null;
for (Position position : traversalPolicy) {
switch (position) {
case TOP: r = positionAbove(d); break;
case BOTTOM: r = positionUnder(d); break;
case LEFT: r = positionLeft(d); break;
case RIGHT: r = positionRight(d); break;
}
if (myScreenRect.contains(r)) {
popupRect = r;
break;
}
}
if (popupRect != null) {
if (popup.isVisible()) {
popup.setLocation(new Point(r.x, r.y));
}
else {
final Point p = new Point(r.x - myRelativeOnScreen.x, r.y - myRelativeOnScreen.y);
popup.show(new RelativePoint(myRelativeTo, p));
}
}
else {
// ok, popup does not fit, will try to resize it
final java.util.List<Rectangle> boxes = new ArrayList<Rectangle>();
// right
boxes.add(crop(myScreenRect, new Rectangle(myRelativeOnScreen.x + myRelativeTo.getWidth() + myGap, myRelativeOnScreen.y,
myScreenRect.width, myScreenRect.height)));
// left
boxes.add(crop(myScreenRect, new Rectangle(myScreenRect.x, myRelativeOnScreen.y, myRelativeOnScreen.x - myScreenRect.x - myGap,
myScreenRect.height)));
// top
boxes.add(crop(myScreenRect, new Rectangle(myRelativeOnScreen.x, myScreenRect.y,
myScreenRect.width, getYForTopPositioning() - myScreenRect.y - myGap)));
// bottom
boxes.add(crop(myScreenRect, new Rectangle(myRelativeOnScreen.x, myRelativeOnScreen.y + myRelativeTo.getHeight() + myGap,
myScreenRect.width, myScreenRect.height)));
Collections.sort(boxes, new Comparator<Rectangle>() {
@Override
public int compare(final Rectangle o1, final Rectangle o2) {
final int i = new Integer(o1.width).compareTo(o2.width);
return i == 0 ? new Integer(o1.height).compareTo(o2.height) : i;
}
});
final Rectangle suitableBox = boxes.get(boxes.size() - 1);
final Rectangle crop = crop(suitableBox,
new Rectangle(suitableBox.x < myRelativeOnScreen.x ? suitableBox.x + suitableBox.width - d.width :
suitableBox.x, suitableBox.y < myRelativeOnScreen.y
? suitableBox.y + suitableBox.height - d.height
: suitableBox.y,
d.width, d.height));
popup.setSize(crop.getSize());
if (popup.isVisible()) {
popup.setLocation(crop.getLocation());
}
else {
popup.show(new RelativePoint(myRelativeTo, new Point(crop.getLocation().x - myRelativeOnScreen.x,
crop.getLocation().y - myRelativeOnScreen.y)));
}
}
}
private static Rectangle crop(final Rectangle source, final Rectangle toCrop) {
final Rectangle result = new Rectangle(toCrop);
if (toCrop.x < source.x) {
result.width -= source.x - toCrop.x;
result.x = source.x;
}
if (toCrop.y < source.y) {
result.height -= source.y - toCrop.y;
result.y = source.y;
}
if (result.x + result.width > source.x + source.width) {
result.width = source.x + source.width - result.x;
}
if (result.y + result.height > source.y + source.height) {
result.height = source.y + source.height - result.y;
}
return result;
}
public static Dimension getPopupSize(final JBPopup popup) {
Dimension size = null;
if (popup instanceof AbstractPopup) {
final String dimensionKey = ((AbstractPopup)popup).getDimensionServiceKey();
if (dimensionKey != null) {
size = DimensionService.getInstance().getSize(dimensionKey);
}
}
if (size == null) {
size = popup.getContent().getPreferredSize();
}
return size;
}
}
}