/* * 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.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.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 myMouseListeners = new ArrayList(); private final Set mySortedMouseListeners = new TreeSet(new Comparator() { @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 myPainters = new LinkedHashSet(); private final Map myPainter2Component = new LinkedHashMap(); private boolean myPaintingActive; private boolean myPreprocessorActive; private final Map myListener2Cursor = new LinkedHashMap(); 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 MouseEvent myLastRedispatchedEvent = null; private boolean preprocess(final MouseEvent e, final boolean motion, JRootPane eventRootPane) { try { final MouseEvent event = convertEvent(e, eventRootPane); if (!IdeGlassPaneUtil.canBePreprocessed(e)) { return false; } Component c = SwingUtilities.getDeepestComponentAt(e.getComponent(), e.getX(), e.getY()); Balloon balloon = JBPopupFactory.getInstance().getParentBalloonFor(c); if (balloon != null && myLastRedispatchedEvent != e) { if (e.getID() == MouseEvent.MOUSE_PRESSED && IdeTooltipManager.getInstance().hasCurrent() && IdeTooltipManager.getInstance().hideCurrent(event, null, null, false)) { //noinspection SSBasedInspection SwingUtilities.invokeLater(new Runnable() { @Override public void run() { myLastRedispatchedEvent = e; IdeEventQueue.getInstance().dispatchEvent(e); } }); } return false; } myLastRedispatchedEvent = null; 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; } }