/*
* Copyright 2000-2017 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.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Weighted;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeGlassPane;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.ui.BalloonImpl;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.ui.EmptyClipboardOwner;
import com.intellij.util.ui.MouseEventAdapter;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.*;
import java.util.List;
public class IdeGlassPaneImpl extends JPanel implements IdeGlassPaneEx, IdeEventQueue.EventDispatcher {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.IdeGlassPaneImpl");
private static final String PREPROCESSED_CURSOR_KEY = "SuperCursor";
private final List<EventListener> myMouseListeners = new ArrayList<>();
private final Set<EventListener> mySortedMouseListeners = new TreeSet<>((o1, 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 Map<String, PaintersHelper> myNamedPainters = new FactoryMap<String, PaintersHelper>() {
@Nullable
@Override
protected PaintersHelper create(String key) {
return new PaintersHelper(IdeGlassPaneImpl.this);
}
};
private boolean myPreprocessorActive;
private final Map<Object, Cursor> myListener2Cursor = new LinkedHashMap<>();
private Component myLastCursorComponent;
private Cursor myLastOriginalCursor;
private MouseEvent myPrevPressEvent;
public IdeGlassPaneImpl(JRootPane rootPane) {
this(rootPane, false);
}
public IdeGlassPaneImpl(JRootPane rootPane, boolean installPainters) {
myRootPane = rootPane;
setOpaque(false);
setVisible(false);
setLayout(null);
if (installPainters) {
IdeBackgroundUtil.initFramePainters(this);
IdeBackgroundUtil.initEditorPainters(this);
}
}
@Override
public void addNotify() {
super.addNotify();
}
public boolean dispatch(@NotNull final AWTEvent e) {
JRootPane eventRootPane = myRootPane;
if (e instanceof MouseEvent) {
MouseEvent me = (MouseEvent)e;
Window eventWindow = UIUtil.getWindow(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);
}
}
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 = UIUtil.getWindow(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 = MouseEventAdapter.convert(me, target);
switch (me.getID()) {
case MouseEvent.MOUSE_PRESSED:
boolean consumed = false;
if (target.isFocusable()) {
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
IdeFocusManager.getGlobalInstance().requestFocus(target, true);
});
}
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 {
if (UIUtil.getWindow(this) != UIUtil.getWindow(e.getComponent())) return false;
final MouseEvent event = MouseEventAdapter.convert(e, eventRootPane);
if (event.isAltDown() && SwingUtilities.isLeftMouseButton(event) && event.getID() == MouseEvent.MOUSE_PRESSED) {
Component c = SwingUtilities.getDeepestComponentAt(e.getComponent(), e.getX(), e.getY());
Balloon balloon = JBPopupFactory.getInstance().getParentBalloonFor(c);
if (balloon instanceof BalloonImpl) {
JComponent component = ((BalloonImpl)balloon).getComponent();
component.getToolkit().getSystemClipboard().setContents(new StringSelection(UIUtil.getDebugText(component)), EmptyClipboardOwner.INSTANCE);
}
}
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())) {
if (target instanceof JComponent) {
((JComponent)target).putClientProperty(PREPROCESSED_CURSOR_KEY, Boolean.TRUE);
}
target.setCursor(cursor);
}
}
getRootPane().setCursor(cursor);
}
}
else if (!e.isConsumed() && e.getID() != MouseEvent.MOUSE_DRAGGED) {
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);
if (myLastCursorComponent instanceof JComponent) {
((JComponent)myLastCursorComponent).putClientProperty(PREPROCESSED_CURSOR_KEY, null);
}
}
}
public static boolean hasPreProcessedCursor(@NotNull JComponent component) {
return component.getClientProperty(PREPROCESSED_CURSOR_KEY) != null;
}
public void setCursor(Cursor cursor, @NotNull Object requestor) {
if (cursor == null) {
myListener2Cursor.remove(requestor);
}
else {
myListener2Cursor.put(requestor, cursor);
}
}
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(() -> 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 (myPreprocessorActive && myMouseListeners.isEmpty()) {
myPreprocessorActive = false;
}
applyActivationState();
}
private void activateIfNeeded() {
if (!myPreprocessorActive && !myMouseListeners.isEmpty()) {
myPreprocessorActive = true;
}
applyActivationState();
}
private void applyActivationState() {
boolean wasVisible = isVisible();
boolean hasWork = getPainters().hasPainters() || getComponentCount() > 0;
if (wasVisible != hasWork) {
setVisible(hasWork);
}
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();
}
}
@NotNull
PaintersHelper getNamedPainters(@NotNull String name) {
return myNamedPainters.get(name);
}
@NotNull
private PaintersHelper getPainters() {
return getNamedPainters("glass");
}
public void addPainter(final Component component, final Painter painter, final Disposable parent) {
getPainters().addPainter(painter, component);
activateIfNeeded();
Disposer.register(parent, new Disposable() {
public void dispose() {
SwingUtilities.invokeLater(() -> removePainter(painter));
}
});
}
public void removePainter(final Painter painter) {
getPainters().removePainter(painter);
deactivateIfNeeded();
}
@Override
protected void addImpl(Component comp, Object constraints, int index) {
super.addImpl(comp, constraints, index);
SwingUtilities.invokeLater(() -> activateIfNeeded());
}
@Override
public void remove(final Component comp) {
super.remove(comp);
SwingUtilities.invokeLater(() -> 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) {
getPainters().paint(g);
}
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
}
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 !getPainters().hasPainters() && super.isOptimizedDrawingEnabled();
}
}