blob: 1613953287b6639e4a66279cfd7634d0a22c43d9 [file] [log] [blame]
/*
* Copyright 2000-2014 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.wm.impl;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.ide.dnd.DnDAware;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Divider;
import com.intellij.openapi.ui.Painter;
import com.intellij.openapi.ui.impl.GlassPaneDialogWrapperPeer;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Weighted;
import com.intellij.openapi.wm.IdeGlassPane;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.MenuDragMouseEvent;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
public class IdeGlassPaneImpl extends JPanel implements IdeGlassPaneEx, IdeEventQueue.EventDispatcher, Painter.Listener {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.IdeGlassPaneImpl");
private final List<EventListener> myMouseListeners = new ArrayList<EventListener>();
private final Set<EventListener> mySortedMouseListeners = new TreeSet<EventListener>(new Comparator<EventListener>() {
@Override
public int compare(EventListener o1, EventListener o2) {
double weight1 = 0;
double weight2 = 0;
if (o1 instanceof Weighted) {
weight1 = ((Weighted)o1).getWeight();
}
if (o2 instanceof Weighted) {
weight2 = ((Weighted)o2).getWeight();
}
return weight1 > weight2 ? 1 : weight1 < weight2 ? -1 : myMouseListeners.indexOf(o1) - myMouseListeners.indexOf(o2);
}
});
private final JRootPane myRootPane;
private final Set<Painter> myPainters = new LinkedHashSet<Painter>();
private final Map<Painter, Component> myPainter2Component = new LinkedHashMap<Painter, Component>();
private boolean myPaintingActive;
private boolean myPreprocessorActive;
private final Map<Object, Cursor> myListener2Cursor = new LinkedHashMap<Object, Cursor>();
private Component myLastCursorComponent;
private Cursor myLastOriginalCursor;
private MouseEvent myPrevPressEvent;
private JPanel myFocusProxy = new JPanel(){
@Override
public String toString() {
return "FocusProxy";
}
};
public IdeGlassPaneImpl(JRootPane rootPane) {
myRootPane = rootPane;
setOpaque(false);
setVisible(false);
setLayout(null);
myFocusProxy.setOpaque(false);
myFocusProxy.setPreferredSize(new Dimension(0, 0));
myFocusProxy.setFocusable(true);
UIUtil.setFocusProxy(myFocusProxy, true);
}
@Override
public void addNotify() {
super.addNotify();
if (myFocusProxy.getParent() != null) {
myFocusProxy.getParent().remove(myFocusProxy);
}
if (myFocusProxy.getParent() != getParent()) {
getParent().add(myFocusProxy);
myFocusProxy.setBounds(0, 0, 0, 0);
}
}
public boolean dispatch(final AWTEvent e) {
JRootPane eventRootPane = myRootPane;
if (e instanceof MouseEvent) {
MouseEvent me = (MouseEvent)e;
Window eventWindow =
me.getComponent() instanceof Window ? (Window)me.getComponent() : SwingUtilities.getWindowAncestor(me.getComponent());
if (isContextMenu(eventWindow)) return false;
final Window thisGlassWindow = SwingUtilities.getWindowAncestor(myRootPane);
if (eventWindow instanceof JWindow) {
eventRootPane = ((JWindow)eventWindow).getRootPane();
if (eventRootPane != null) {
if (!(eventRootPane.getGlassPane() instanceof IdeGlassPane)) {
final Container parentWindow = eventWindow.getParent();
if (parentWindow instanceof Window) {
eventWindow = (Window)parentWindow;
}
}
}
}
if (eventWindow != thisGlassWindow) return false;
}
if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
if (ApplicationManager.getApplication() != null) {
IdeTooltipManager.getInstance().hideCurrent((MouseEvent)e, null, null);
}
}
boolean dispatched;
if (e.getID() == MouseEvent.MOUSE_PRESSED || e.getID() == MouseEvent.MOUSE_RELEASED || e.getID() == MouseEvent.MOUSE_CLICKED) {
dispatched = preprocess((MouseEvent)e, false, eventRootPane);
}
else if (e.getID() == MouseEvent.MOUSE_MOVED || e.getID() == MouseEvent.MOUSE_DRAGGED) {
dispatched = preprocess((MouseEvent)e, true, eventRootPane);
}
else if (e.getID() == MouseEvent.MOUSE_EXITED || e.getID() == MouseEvent.MOUSE_ENTERED) {
dispatched = preprocess((MouseEvent)e, false, eventRootPane);
}
else {
return false;
}
MouseEvent me = (MouseEvent)e;
final Component meComponent = me.getComponent();
if (!dispatched && meComponent != null) {
final Window eventWindow = meComponent instanceof Window ? (Window)meComponent : SwingUtilities.getWindowAncestor(meComponent);
if (eventWindow != SwingUtilities.getWindowAncestor(myRootPane)) {
return false;
}
int button1 = MouseEvent.BUTTON1_MASK | MouseEvent.BUTTON1_DOWN_MASK;
final boolean pureMouse1Event = (me.getModifiersEx() | button1) == button1;
if (pureMouse1Event && me.getClickCount() <= 1 && !me.isPopupTrigger()) {
final Point point = SwingUtilities.convertPoint(meComponent, me.getPoint(), myRootPane.getContentPane());
JMenuBar menuBar = myRootPane.getJMenuBar();
point.y += menuBar != null ? menuBar.getHeight() : 0;
final Component target =
SwingUtilities.getDeepestComponentAt(myRootPane.getContentPane().getParent(), point.x, point.y);
if (target instanceof DnDAware) {
final Point targetPoint = SwingUtilities.convertPoint(myRootPane.getContentPane().getParent(), point.x, point.y, target);
final boolean overSelection = ((DnDAware)target).isOverSelection(targetPoint);
if (overSelection) {
final MouseListener[] listeners = target.getListeners(MouseListener.class);
final MouseEvent mouseEvent = convertEvent(me, target);
switch (me.getID()) {
case MouseEvent.MOUSE_PRESSED:
boolean consumed = false;
if (target.isFocusable()) target.requestFocus();
for (final MouseListener listener : listeners) {
final String className = listener.getClass().getName();
if (className.indexOf("BasicTreeUI$") >= 0 || className.indexOf("MacTreeUI$") >= 0) continue;
fireMouseEvent(listener, mouseEvent);
if (mouseEvent.isConsumed()) {
consumed = true;
break;
}
}
if (!mouseEvent.isConsumed()) {
final AWTEventListener[] eventListeners = Toolkit.getDefaultToolkit().getAWTEventListeners(MouseEvent.MOUSE_EVENT_MASK);
if (eventListeners != null && eventListeners.length > 0) {
for (final AWTEventListener eventListener : eventListeners) {
eventListener.eventDispatched(me);
if (me.isConsumed()) break;
}
if (me.isConsumed()) {
consumed = true;
break;
}
}
}
if (!consumed) {
myPrevPressEvent = mouseEvent;
}
else {
me.consume();
}
dispatched = true;
break;
case MouseEvent.MOUSE_RELEASED:
if (myPrevPressEvent != null && myPrevPressEvent.getComponent() == target) {
for (final MouseListener listener : listeners) {
final String className = listener.getClass().getName();
if (className.indexOf("BasicTreeUI$") >= 0 || className.indexOf("MacTreeUI$") >= 0) {
fireMouseEvent(listener, myPrevPressEvent);
fireMouseEvent(listener, mouseEvent);
if (mouseEvent.isConsumed()) {
break;
}
}
fireMouseEvent(listener, mouseEvent);
if (mouseEvent.isConsumed()) {
break;
}
}
if (mouseEvent.isConsumed()) {
me.consume();
}
myPrevPressEvent = null;
dispatched = true;
}
break;
default:
myPrevPressEvent = null;
break;
}
}
}
}
}
if (isVisible() && getComponentCount() == 0) {
boolean cursorSet = false;
if (meComponent != null) {
final Point point = SwingUtilities.convertPoint(meComponent, me.getPoint(), myRootPane.getContentPane());
if (myRootPane.getMenuBar() != null && myRootPane.getMenuBar().isVisible()) {
point.y += myRootPane.getMenuBar().getHeight();
}
final Component target =
SwingUtilities.getDeepestComponentAt(myRootPane.getContentPane().getParent(), point.x, point.y);
if (target != null) {
setCursor(target.getCursor());
cursorSet = true;
}
}
if (!cursorSet) {
setCursor(Cursor.getDefaultCursor());
}
}
return dispatched;
}
private static boolean isContextMenu(Window window) {
if (window != null) {
for (Component component : window.getComponents()) {
if (component instanceof JComponent
&& UIUtil.findComponentOfType((JComponent)component, JPopupMenu.class) != null) {
return true;
}
}
}
return false;
}
private boolean preprocess(final MouseEvent e, final boolean motion, JRootPane eventRootPane) {
try {
final MouseEvent event = convertEvent(e, eventRootPane);
if (!IdeGlassPaneUtil.canBePreprocessed(e)) {
return false;
}
for (EventListener each : mySortedMouseListeners) {
if (motion && each instanceof MouseMotionListener) {
fireMouseMotion((MouseMotionListener)each, event);
}
else if (!motion && each instanceof MouseListener) {
fireMouseEvent((MouseListener)each, event);
}
if (event.isConsumed()) {
e.consume();
return true;
}
}
return false;
}
finally {
if (eventRootPane == myRootPane) {
Cursor cursor;
if (!myListener2Cursor.isEmpty()) {
cursor = myListener2Cursor.values().iterator().next();
final Point point = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), myRootPane.getContentPane());
Component target =
SwingUtilities.getDeepestComponentAt(myRootPane.getContentPane().getParent(), point.x, point.y);
if (canProcessCursorFor(target)) {
target = getCompWithCursor(target);
restoreLastComponent(target);
if (target != null) {
if (myLastCursorComponent != target) {
myLastCursorComponent = target;
myLastOriginalCursor = target.getCursor();
}
if (cursor != null && !cursor.equals(target.getCursor())) {
target.setCursor(cursor);
}
}
getRootPane().setCursor(cursor);
}
}
else {
cursor = Cursor.getDefaultCursor();
JRootPane rootPane = getRootPane();
if (rootPane != null) {
rootPane.setCursor(cursor);
} else {
LOG.warn("Root pane is null. Event: " + e);
}
restoreLastComponent(null);
myLastOriginalCursor = null;
myLastCursorComponent = null;
}
myListener2Cursor.clear();
}
}
}
private boolean canProcessCursorFor(Component target) {
if (target instanceof JMenu ||
target instanceof JMenuItem ||
target instanceof Divider ||
target instanceof JSeparator ||
(target instanceof JEditorPane && ((JEditorPane)target).getEditorKit() instanceof HTMLEditorKit)) {
return false;
}
return true;
}
private Component getCompWithCursor(Component c) {
Component eachParentWithCursor = c;
while (eachParentWithCursor != null) {
if (eachParentWithCursor.isCursorSet()) return eachParentWithCursor;
eachParentWithCursor = eachParentWithCursor.getParent();
}
return null;
}
private void restoreLastComponent(Component newC) {
if (myLastCursorComponent != null && myLastCursorComponent != newC) {
myLastCursorComponent.setCursor(myLastOriginalCursor);
}
}
public void setCursor(Cursor cursor, @NotNull Object requestor) {
if (cursor == null) {
myListener2Cursor.remove(requestor);
}
else {
myListener2Cursor.put(requestor, cursor);
}
}
private static MouseEvent convertEvent(final MouseEvent e, final Component target) {
final Point point = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), target);
if (e instanceof MouseWheelEvent) {
final MouseWheelEvent mwe = (MouseWheelEvent)e;
return new MouseWheelEvent(target, mwe.getID(), mwe.getWhen(), mwe.getModifiersEx(), point.x, point.y, mwe.getClickCount(),
mwe.isPopupTrigger(), mwe.getScrollType(), mwe.getScrollAmount(), mwe.getWheelRotation());
}
else if (e instanceof MenuDragMouseEvent) {
final MenuDragMouseEvent de = (MenuDragMouseEvent)e;
return new MenuDragMouseEvent(target, de.getID(), de.getWhen(), de.getModifiersEx(), point.x, point.y, e.getClickCount(),
e.isPopupTrigger(), de.getPath(), de.getMenuSelectionManager());
}
else {
return new MouseEvent(target, e.getID(), e.getWhen(), e.getModifiersEx(), point.x, point.y, e.getClickCount(), e.isPopupTrigger(),
e.getButton());
}
}
private static void fireMouseEvent(final MouseListener listener, final MouseEvent event) {
switch (event.getID()) {
case MouseEvent.MOUSE_PRESSED:
listener.mousePressed(event);
break;
case MouseEvent.MOUSE_RELEASED:
listener.mouseReleased(event);
break;
case MouseEvent.MOUSE_ENTERED:
listener.mouseEntered(event);
break;
case MouseEvent.MOUSE_EXITED:
listener.mouseExited(event);
break;
case MouseEvent.MOUSE_CLICKED:
listener.mouseClicked(event);
break;
}
}
private static void fireMouseMotion(MouseMotionListener listener, final MouseEvent event) {
switch (event.getID()) {
case MouseEvent.MOUSE_DRAGGED:
listener.mouseDragged(event);
case MouseEvent.MOUSE_MOVED:
listener.mouseMoved(event);
}
}
public void addMousePreprocessor(final MouseListener listener, Disposable parent) {
_addListener(listener, parent);
}
public void addMouseMotionPreprocessor(final MouseMotionListener listener, final Disposable parent) {
_addListener(listener, parent);
}
private void _addListener(final EventListener listener, final Disposable parent) {
if (!myMouseListeners.contains(listener)) {
myMouseListeners.add(listener);
updateSortedList();
}
activateIfNeeded();
Disposer.register(parent, new Disposable() {
public void dispose() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
removeListener(listener);
}
});
}
});
}
public void removeMousePreprocessor(final MouseListener listener) {
removeListener(listener);
}
public void removeMouseMotionPreprocessor(final MouseMotionListener listener) {
removeListener(listener);
}
private void removeListener(final EventListener listener) {
if (myMouseListeners.remove(listener)) {
updateSortedList();
}
deactivateIfNeeded();
}
private void updateSortedList() {
mySortedMouseListeners.clear();
mySortedMouseListeners.addAll(myMouseListeners);
}
private void deactivateIfNeeded() {
if (myPaintingActive) {
if (myPainters.isEmpty() && getComponentCount() == 0) {
myPaintingActive = false;
}
}
if (myPreprocessorActive && myMouseListeners.isEmpty()) {
myPreprocessorActive = false;
}
applyActivationState();
}
private void activateIfNeeded() {
if (!myPaintingActive) {
if (!myPainters.isEmpty() || getComponentCount() > 0) {
myPaintingActive = true;
}
}
if (!myPreprocessorActive && !myMouseListeners.isEmpty()) {
myPreprocessorActive = true;
}
applyActivationState();
}
private void applyActivationState() {
boolean wasVisible = isVisible();
if (wasVisible != myPaintingActive) {
setVisible(myPaintingActive);
}
IdeEventQueue queue = IdeEventQueue.getInstance();
if (!queue.containsDispatcher(this) && (myPreprocessorActive || isVisible())) {
queue.addDispatcher(this, null);
}
else if (queue.containsDispatcher(this) && !myPreprocessorActive && !isVisible()) {
queue.removeDispatcher(this);
}
if (wasVisible != isVisible()) {
revalidate();
repaint();
}
}
public void addPainter(final Component component, final Painter painter, final Disposable parent) {
myPainters.add(painter);
myPainter2Component.put(painter, component == null ? this : component);
painter.addListener(this);
activateIfNeeded();
Disposer.register(parent, new Disposable() {
public void dispose() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
removePainter(painter);
}
});
}
});
}
public void removePainter(final Painter painter) {
myPainters.remove(painter);
myPainter2Component.remove(painter);
painter.removeListener(this);
deactivateIfNeeded();
}
@Override
protected void addImpl(Component comp, Object constraints, int index) {
super.addImpl(comp, constraints, index);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
activateIfNeeded();
}
});
}
@Override
public void remove(final Component comp) {
super.remove(comp);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
deactivateIfNeeded();
}
});
}
public boolean isInModalContext() {
final Component[] components = getComponents();
for (Component component : components) {
if (component instanceof GlassPaneDialogWrapperPeer.TransparentLayeredPane) {
return true;
}
}
return false;
}
protected void paintComponent(final Graphics g) {
if (myPainters.isEmpty()) return;
Graphics2D g2d = (Graphics2D)g;
for (Painter painter : myPainters) {
final Rectangle clip = g.getClipBounds();
final Component component = myPainter2Component.get(painter);
if (component.getParent() == null) continue;
final Rectangle componentBounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), this);
if (!painter.needsRepaint()) {
continue;
}
if (clip.contains(componentBounds) || clip.intersects(componentBounds)) {
final Point targetPoint = SwingUtilities.convertPoint(this, 0, 0, component);
final Rectangle targetRect = new Rectangle(targetPoint, component.getSize());
g2d.translate(-targetRect.x, -targetRect.y);
painter.paint(component, g2d);
g2d.translate(targetRect.x, targetRect.y);
}
}
}
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
}
public boolean hasPainters() {
return !myPainters.isEmpty();
}
public void onNeedsRepaint(final Painter painter, final JComponent dirtyComponent) {
if (dirtyComponent != null && dirtyComponent.isShowing()) {
final Rectangle rec = SwingUtilities.convertRectangle(dirtyComponent, dirtyComponent.getBounds(), this);
if (rec != null) {
repaint(rec);
return;
}
}
repaint();
}
public Component getTargetComponentFor(MouseEvent e) {
Component candidate = findComponent(e, myRootPane.getLayeredPane());
if (candidate != null) return candidate;
candidate = findComponent(e, myRootPane.getContentPane());
if (candidate != null) return candidate;
return e.getComponent();
}
private static Component findComponent(final MouseEvent e, final Container container) {
final Point lpPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), container);
return SwingUtilities.getDeepestComponentAt(container, lpPoint.x, lpPoint.y);
}
@Override
public boolean isOptimizedDrawingEnabled() {
return !hasPainters() && super.isOptimizedDrawingEnabled();
}
@Override
public JComponent getProxyComponent() {
return myFocusProxy;
}
}