blob: df91efed9579c31486aae7f8b932e41c0e92e61b [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.debugger.ui.impl;
import com.intellij.ide.FrameStateListener;
import com.intellij.ide.FrameStateManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Weighted;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.IdeGlassPane;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.util.Alarm;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.Activatable;
import com.intellij.util.ui.update.UiNotifyConnector;
import com.intellij.xdebugger.settings.XDebuggerSettingsManager;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.*;
/**
* @author lex
*/
public class TipManager implements Disposable, PopupMenuListener {
private volatile boolean myIsDisposed = false;
private boolean myPopupShown;
private MyAwtPreprocessor myHideCanceller;
private MouseEvent myLastMouseEvent;
public interface TipFactory {
JComponent createToolTip (MouseEvent e);
MouseEvent createTooltipEvent(MouseEvent candidateEvent);
boolean isFocusOwner();
}
private boolean isOverTip(MouseEvent e) {
if (myCurrentTooltip != null) {
if(!myCurrentTooltip.isShowing()) {
hideTooltip(true);
return false;
}
final Component eventOriginator = e.getComponent();
if (eventOriginator == null) {
return false;
}
final Point point = e.getPoint();
SwingUtilities.convertPointToScreen(point, eventOriginator);
final Rectangle bounds = myCurrentTooltip.getBounds();
final Point tooltipLocationOnScreen = myCurrentTooltip.getLocationOnScreen();
bounds.setLocation(tooltipLocationOnScreen.x, tooltipLocationOnScreen.y);
return bounds.contains(point);
}
return false;
}
boolean myInsideComponent;
private class MyMouseListener extends MouseAdapter implements Weighted{
@Override
public void mouseExited(final MouseEvent e) {
myInsideComponent = false;
}
@Override
public void mousePressed(final MouseEvent e) {
if (myInsideComponent) {
hideTooltip(true);
}
}
@Override
public double getWeight() {
return 0;
}
@Override
public void mouseEntered(final MouseEvent e) {
myInsideComponent = true;
}
}
private class MyFrameStateListener extends FrameStateListener.Adapter {
@Override
public void onFrameDeactivated() {
hideTooltip(true);
}
}
public JPopupMenu registerPopup(JPopupMenu menu) {
menu.addPopupMenuListener(this);
return menu;
}
@Override
public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
myPopupShown = true;
}
@Override
public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
onPopupClosed(e);
}
@Override
public void popupMenuCanceled(final PopupMenuEvent e) {
onPopupClosed(e);
}
private void onPopupClosed(final PopupMenuEvent e) {
myPopupShown = false;
if (e.getSource() instanceof JPopupMenu) {
((JPopupMenu)e.getSource()).removePopupMenuListener(this);
}
}
private class MyMouseMotionListener extends MouseMotionAdapter implements Weighted{
@Override
public void mouseMoved(final MouseEvent e) {
myLastMouseEvent = e;
if (!myComponent.isShowing()) return;
myInsideComponent = true;
if (myCurrentTooltip == null) {
if (isInsideComponent(e)) {
tryTooltip(e, true);
}
} else {
if (!isOverTip(e)) {
tryTooltip(e, true);
}
}
}
@Override
public double getWeight() {
return 0;
}
}
private boolean isInsideComponent(final MouseEvent e) {
final Rectangle compBounds = myComponent.getVisibleRect();
final Point compPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), myComponent);
return compBounds.contains(compPoint);
}
private void tryTooltip(final InputEvent e, final boolean auto) {
myShowAlarm.cancelAllRequests();
myHideAlarm.cancelAllRequests();
myShowAlarm.addRequest(new Runnable() {
@Override
public void run() {
if (!myIsDisposed && !myPopupShown) {
showTooltip(e, auto);
}
}
}, auto ? XDebuggerSettingsManager.getInstance().getDataViewSettings().getValueLookupDelay() : 10);
}
private void showTooltip(InputEvent e, boolean auto) {
if (auto && !Registry.is("debugger.valueTooltipAutoShow")) return;
MouseEvent sourceEvent = null;
JComponent newTip = null;
if (e instanceof MouseEvent) {
sourceEvent = (MouseEvent)e;
} else if (e instanceof KeyEvent) {
sourceEvent = myTipFactory.createTooltipEvent(myLastMouseEvent);
}
MouseEvent convertedEvent = null;
if (sourceEvent != null) {
convertedEvent = SwingUtilities.convertMouseEvent(sourceEvent.getComponent(), sourceEvent, myComponent);
newTip = myTipFactory.createToolTip(convertedEvent);
}
if (newTip == null || (auto && !myTipFactory.isFocusOwner())) {
hideTooltip(false);
return;
}
if(newTip == myCurrentTooltip) {
if (!auto) {
hideTooltip(true);
return;
}
return;
}
hideTooltip(true);
if(myComponent.isShowing()) {
PopupFactory popupFactory = PopupFactory.getSharedInstance();
final Point location = convertedEvent.getPoint();
final Component sourceComponent = convertedEvent.getComponent();
if (sourceComponent != null) {
SwingUtilities.convertPointToScreen(location, sourceComponent);
}
myTipPopup = popupFactory.getPopup(myComponent, newTip, location.x, location.y);
myInsideComponent = false;
myTipPopup.show();
myCurrentTooltip = newTip;
}
}
public void hideTooltip() {
hideTooltip(true);
}
public void hideTooltip(boolean now) {
if (myTipPopup == null) return;
if (now) {
myHideAlarm.cancelAllRequests();
myTipPopup.hide();
myTipPopup = null;
myCurrentTooltip = null;
} else {
myHideAlarm.addRequest(new Runnable() {
@Override
public void run() {
if (myInsideComponent) {
hideTooltip(true);
}
}
}, 100);
}
}
private JComponent myCurrentTooltip;
private Popup myTipPopup;
private final TipFactory myTipFactory;
private final JComponent myComponent;
private MouseListener myMouseListener = new MyMouseListener();
private MouseMotionListener myMouseMotionListener = new MyMouseMotionListener();
private FrameStateListener myFrameStateListener = new MyFrameStateListener();
private final Alarm myShowAlarm = new Alarm();
private final Alarm myHideAlarm = new Alarm();
private IdeGlassPane myGP;
public TipManager(final JComponent component, TipFactory factory) {
myTipFactory = factory;
myComponent = component;
new UiNotifyConnector.Once(component, new Activatable() {
@Override
public void showNotify() {
installListeners();
}
@Override
public void hideNotify() {
}
});
final HideTooltipAction hide = new HideTooltipAction();
hide.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)), myComponent);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
hide.unregisterCustomShortcutSet(myComponent);
}
});
}
private class HideTooltipAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
hideTooltip(true);
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(myTipPopup != null);
}
}
private void installListeners() {
if (myIsDisposed) return;
myGP = IdeGlassPaneUtil.find(myComponent);
assert myGP != null;
myGP.addMousePreprocessor(myMouseListener, this);
myGP.addMouseMotionPreprocessor(myMouseMotionListener, this);
myHideCanceller = new MyAwtPreprocessor();
Toolkit.getDefaultToolkit().addAWTEventListener(myHideCanceller, AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
FrameStateManager.getInstance().addListener(myFrameStateListener);
}
@Override
public void dispose() {
Disposer.dispose(this);
hideTooltip(true);
Toolkit.getDefaultToolkit().removeAWTEventListener(myHideCanceller);
myIsDisposed = true;
myShowAlarm.cancelAllRequests();
myMouseListener = null;
myMouseMotionListener = null;
FrameStateManager.getInstance().removeListener(myFrameStateListener);
myFrameStateListener = null;
}
private class MyAwtPreprocessor implements AWTEventListener {
@Override
public void eventDispatched(AWTEvent event) {
if (event.getID() == MouseEvent.MOUSE_MOVED) {
preventFromHideIfInsideTooltip(event);
} else if (event.getID() == MouseEvent.MOUSE_PRESSED || event.getID() == MouseEvent.MOUSE_RELEASED) {
hideTooltipIfCloseClick((MouseEvent)event);
} else if (event instanceof KeyEvent) {
tryToShowTooltipIfRequested((KeyEvent)event);
}
}
private void hideTooltipIfCloseClick(MouseEvent me) {
if (myCurrentTooltip == null) return;
if (isInsideTooltip(me) && UIUtil.isCloseClick(me)) {
hideTooltip(true);
}
}
private void tryToShowTooltipIfRequested(KeyEvent event) {
if (KeymapUtil.isTooltipRequest(event)) {
tryTooltip(event, false);
} else {
if (event.getID() == KeyEvent.KEY_PRESSED) {
myLastMouseEvent = null;
}
}
}
private void preventFromHideIfInsideTooltip(AWTEvent event) {
if (myCurrentTooltip == null) return;
if (event.getID() == MouseEvent.MOUSE_MOVED) {
final MouseEvent me = (MouseEvent)event;
if (isInsideTooltip(me)) {
myHideAlarm.cancelAllRequests();
}
}
}
private boolean isInsideTooltip(MouseEvent me) {
return myCurrentTooltip == me.getComponent() || SwingUtilities.isDescendingFrom(me.getComponent(), myCurrentTooltip);
}
}
}