| /* |
| * Copyright 2000-2009 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.messager; |
| |
| |
| import com.intellij.ui.DrawUtil; |
| import com.intellij.ui.LineEndDecorator; |
| import com.intellij.ui.ScreenUtil; |
| import com.intellij.ui.UI; |
| import com.intellij.ui.awt.RelativePoint; |
| import com.intellij.ui.awt.RelativeRectangle; |
| import com.intellij.ui.components.panels.NonOpaquePanel; |
| import com.intellij.util.ui.UIUtil; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.awt.geom.Line2D; |
| |
| /** |
| * @author kir |
| */ |
| public class CalloutComponent { |
| |
| private static final int POINTER_LENGTH = 20; |
| |
| private final JDialog myFrame; |
| |
| private final JComponent myInnerComponent; |
| |
| private ComponentListener myComponentListener; |
| private WindowListener myWindowListener; |
| private WindowStateListener myWindowStateListener; |
| private AWTEventListener myMulticastListener; |
| private KeyEventDispatcher myKeyEventDispatcher; |
| |
| protected JComponent myTargetComponent; |
| protected Window myTargetWindow; |
| protected Pointer myPointerComponent; |
| private final KeyboardFocusManager myKeyboardFocusManager; |
| |
| public CalloutComponent(JComponent component) { |
| super(); |
| |
| myKeyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); |
| myInnerComponent = component; |
| myInnerComponent.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| |
| myFrame = new JDialog(); |
| myFrame.setUndecorated(true); |
| myFrame.setFocusable(false); |
| myFrame.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); |
| myFrame.setFocusableWindowState(false); |
| |
| myFrame.getContentPane().setLayout(new BorderLayout()); |
| myFrame.getContentPane().add(new Wrapper(myInnerComponent), BorderLayout.CENTER); |
| } |
| |
| public void show(int location, final RelativePoint target) { |
| myFrame.pack(); |
| Dimension frameSize = myFrame.getPreferredSize(); |
| |
| final Point targetScreenPoint = target.getScreenPoint(); |
| Point framePoint = new Point(); |
| |
| switch (location) { |
| case Callout.NORTH_WEST: |
| framePoint.x = targetScreenPoint.x - frameSize.width - getPointerShift(); |
| framePoint.y = targetScreenPoint.y - frameSize.height - getPointerShift(); |
| break; |
| case Callout.NORTH_EAST: |
| framePoint.x = targetScreenPoint.x + getPointerShift(); |
| framePoint.y = targetScreenPoint.y - frameSize.height - getPointerShift(); |
| break; |
| case Callout.SOUTH_EAST: |
| framePoint.x = targetScreenPoint.x + getPointerShift(); |
| framePoint.y = targetScreenPoint.y + getPointerShift(); |
| break; |
| case Callout.SOUTH_WEST: |
| framePoint.x = targetScreenPoint.x - frameSize.width - getPointerShift(); |
| framePoint.y = targetScreenPoint.y + getPointerShift(); |
| break; |
| } |
| |
| myPointerComponent = new Pointer(location); |
| |
| final Rectangle frameBounds = new Rectangle(framePoint, frameSize); |
| ScreenUtil.moveRectangleToFitTheScreen(frameBounds); |
| |
| myTargetComponent = (JComponent) target.getComponent(); |
| myTargetWindow = SwingUtilities.getWindowAncestor(myTargetComponent); |
| |
| final JLayeredPane layered = getLayeredPane(myTargetWindow); |
| final Rectangle layeredBounds = new RelativeRectangle(layered).getScreenRectangle(); |
| |
| final boolean[] outside = getOutsideAxisCodes(layeredBounds, frameBounds); |
| if (outside != null) { |
| boolean x = outside[0]; |
| boolean y = outside[1]; |
| |
| switch (location) { |
| case Callout.NORTH_WEST: |
| if (x) { |
| frameBounds.x = layeredBounds.x - frameBounds.width; |
| } |
| if (y) { |
| frameBounds.y = layeredBounds.y - frameBounds.height; |
| } |
| break; |
| case Callout.NORTH_EAST: |
| if (x) { |
| frameBounds.x = (int) layeredBounds.getMaxX(); |
| } |
| if (y) { |
| frameBounds.y = layeredBounds.y - frameBounds.height; |
| } |
| break; |
| case Callout.SOUTH_EAST: |
| if (x) { |
| frameBounds.x = (int)layeredBounds.getMaxX(); |
| } |
| if (y) { |
| frameBounds.y = (int) layeredBounds.getMaxY(); |
| } |
| break; |
| case Callout.SOUTH_WEST: |
| if (x) { |
| frameBounds.x = layeredBounds.x - frameBounds.width; |
| } |
| if (y) { |
| frameBounds.y = (int) layeredBounds.getMaxY(); |
| } |
| break; |
| } |
| } |
| |
| |
| Point targetLayeredPoint = target.getPoint(layered); |
| Rectangle frameLayeredBounds = RelativeRectangle.fromScreen(layered, frameBounds).getRectangleOn(layered); |
| |
| Rectangle pointerBounds = new Rectangle(); |
| final int extraPoint = 1; |
| switch (location) { |
| case Callout.NORTH_WEST: |
| pointerBounds.x = (int) frameLayeredBounds.getMaxX() - extraPoint; |
| pointerBounds.y = (int) frameLayeredBounds.getMaxY() - extraPoint; |
| pointerBounds.width = targetLayeredPoint.x - pointerBounds.x; |
| pointerBounds.height = targetLayeredPoint.y - pointerBounds.y; |
| break; |
| case Callout.NORTH_EAST: |
| pointerBounds.x = targetLayeredPoint.x; |
| pointerBounds.y = (int) frameLayeredBounds.getMaxY() - extraPoint; |
| pointerBounds.width = frameLayeredBounds.x + extraPoint - targetLayeredPoint.x; |
| pointerBounds.height = targetLayeredPoint.y - pointerBounds.y; |
| break; |
| case Callout.SOUTH_EAST: |
| pointerBounds.x = targetLayeredPoint.x; |
| pointerBounds.y = targetLayeredPoint.y; |
| pointerBounds.width = frameLayeredBounds.x + extraPoint - targetLayeredPoint.x; |
| pointerBounds.height = (int)frameLayeredBounds.getMaxY() + extraPoint - targetLayeredPoint.y - frameLayeredBounds.height; |
| break; |
| case Callout.SOUTH_WEST: |
| pointerBounds.x = (int) frameLayeredBounds.getMaxX() - extraPoint; |
| pointerBounds.y = targetLayeredPoint.y; |
| pointerBounds.width = targetLayeredPoint.x - pointerBounds.x; |
| pointerBounds.height = frameLayeredBounds.y + extraPoint - targetLayeredPoint.y; |
| break; |
| } |
| |
| layered.add(myPointerComponent, JLayeredPane.POPUP_LAYER); |
| myPointerComponent.setBounds(pointerBounds); |
| |
| myFrame.setBounds(frameBounds); |
| myFrame.setVisible(true); |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| installDisposeListeners(); |
| myFrame.setVisible(true); |
| } |
| }); |
| } |
| |
| private boolean[] getOutsideAxisCodes(final Rectangle layeredBounds, final Rectangle frameBounds) { |
| boolean x = frameBounds.getMaxX() < layeredBounds.x || layeredBounds.getMaxX() < frameBounds.x; |
| boolean y = frameBounds.getMaxY() < layeredBounds.y || layeredBounds.getMaxY() < frameBounds.y; |
| |
| if (x || y) { |
| return new boolean[] {x, y}; |
| } else { |
| return null; |
| } |
| } |
| |
| private void installDisposeListeners() { |
| myKeyEventDispatcher = new KeyEventDispatcher() { |
| public boolean dispatchKeyEvent(KeyEvent e) { |
| dispose(); |
| return false; |
| } |
| }; |
| myKeyboardFocusManager.addKeyEventDispatcher(myKeyEventDispatcher); |
| |
| myMulticastListener = new AWTEventListener() { |
| public void eventDispatched(AWTEvent event) { |
| switch (event.getID()) { |
| case MouseEvent.MOUSE_PRESSED: |
| dispose(); |
| } |
| } |
| }; |
| |
| Toolkit.getDefaultToolkit().addAWTEventListener(myMulticastListener, AWTEvent.MOUSE_EVENT_MASK ); |
| |
| myComponentListener = new ComponentListener() { |
| public void componentHidden(ComponentEvent e) { |
| dispose(); |
| } |
| |
| public void componentMoved(ComponentEvent e) { |
| dispose(); |
| } |
| |
| public void componentResized(ComponentEvent e) { |
| dispose(); |
| } |
| |
| public void componentShown(ComponentEvent e) { |
| dispose(); |
| } |
| }; |
| |
| myWindowListener = new WindowListener() { |
| public void windowActivated(WindowEvent e) { |
| dispose(); |
| } |
| |
| public void windowClosed(WindowEvent e) { |
| dispose(); |
| } |
| |
| public void windowClosing(WindowEvent e) { |
| dispose(); |
| } |
| |
| public void windowDeactivated(WindowEvent e) { |
| dispose(); |
| } |
| |
| public void windowDeiconified(WindowEvent e) { |
| dispose(); |
| } |
| |
| public void windowIconified(WindowEvent e) { |
| dispose(); |
| } |
| |
| public void windowOpened(WindowEvent e) { |
| dispose(); |
| } |
| }; |
| myTargetWindow.addWindowListener(myWindowListener); |
| |
| myWindowStateListener = new WindowStateListener() { |
| public void windowStateChanged(WindowEvent e) { |
| dispose(); |
| } |
| }; |
| myTargetWindow.addWindowStateListener(myWindowStateListener); |
| } |
| |
| private void dispose() { |
| |
| Runnable runnable = new Runnable() { |
| public void run() { |
| myFrame.dispose(); |
| |
| Toolkit.getDefaultToolkit().removeAWTEventListener(myMulticastListener); |
| myKeyboardFocusManager.removeKeyEventDispatcher(myKeyEventDispatcher); |
| |
| myTargetComponent.removeComponentListener(myComponentListener); |
| myTargetWindow.removeWindowListener(myWindowListener); |
| myTargetWindow.removeWindowStateListener(myWindowStateListener); |
| |
| final Container parent = myPointerComponent.getParent(); |
| final Rectangle bounds = myPointerComponent.getBounds(); |
| if (parent != null) { |
| parent.remove(myPointerComponent); |
| parent.repaint(bounds.x, bounds.y, bounds.width, bounds.height); |
| } |
| } |
| }; |
| |
| SwingUtilities.invokeLater(runnable); |
| } |
| |
| private int getPointerShift() { |
| return (int) Math.sqrt(POINTER_LENGTH * POINTER_LENGTH / 2); |
| } |
| |
| private Color getFillColor() { |
| return UI.getColor("callout.background"); |
| } |
| |
| private Color getBoundsColor() { |
| return UI.getColor("callout.frame.color"); |
| } |
| |
| private class Wrapper extends NonOpaquePanel { |
| |
| public Wrapper(JComponent component) { |
| setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3)); |
| setLayout(new BorderLayout()); |
| add(component, BorderLayout.CENTER); |
| } |
| |
| protected void paintComponent(Graphics g) { |
| Graphics2D g2 = (Graphics2D) g; |
| |
| final Object old = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); |
| g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| |
| g.setColor(getFillColor()); |
| g.fillRect(1, 1, getWidth() - 2, getHeight() - 2); |
| |
| DrawUtil.drawRoundRect(g, 0, 0, getWidth() - 1, getHeight() - 1, getBoundsColor()); |
| |
| g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old); |
| } |
| } |
| |
| private class Pointer extends NonOpaquePanel { |
| private final int myOrientation; |
| |
| public Pointer(int orientation) { |
| myOrientation = orientation; |
| } |
| |
| protected void paintComponent(Graphics g) { |
| Graphics2D g2 = (Graphics2D) g; |
| |
| final Object old = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); |
| g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| |
| g2.setColor(getBoundsColor()); |
| |
| Line2D line = new Line2D.Double(); |
| switch (myOrientation) { |
| case Callout.NORTH_WEST: |
| line.setLine(0, 0, getWidth() - 1, getHeight() - 1); |
| break; |
| case Callout.NORTH_EAST: |
| line.setLine(getWidth() - 1, 0, 0, getHeight() -1); |
| break; |
| case Callout.SOUTH_EAST: |
| line.setLine(getWidth() - 1, getHeight() - 1, 0, 0); |
| break; |
| case Callout.SOUTH_WEST: |
| line.setLine(0, getHeight() - 1, getWidth() - 1, 0); |
| break; |
| } |
| |
| UIUtil.drawLine(g2, (int)line.getX1(), (int)line.getY1(), (int)line.getX2(), (int)line.getY2()); |
| |
| final Shape arrow = LineEndDecorator.getArrowShape(line, line.getP2()); |
| g2.fill(arrow); |
| |
| g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old); |
| } |
| } |
| |
| private JLayeredPane getLayeredPane(Window window) { |
| if (window instanceof JFrame) { |
| return ((JFrame) window).getRootPane().getLayeredPane(); |
| } else if (window instanceof JDialog) { |
| return ((JDialog) window).getRootPane().getLayeredPane(); |
| } |
| |
| return null; |
| } |
| |
| } |