/* * Copyright 2000-2012 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.ui.UISettings; import com.intellij.ide.ui.UISettingsListener; import com.intellij.openapi.Disposable; import com.intellij.openapi.MnemonicHelper; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.ui.ScreenUtil; import com.intellij.util.Alarm; import com.intellij.util.ui.UIUtil; import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; /** * @author Anton Katilin * @author Vladimir Kondratyev */ public final class FloatingDecorator extends JDialog { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.FloatingDecorator"); static final int DIVIDER_WIDTH = 3; private static final int ANCHOR_TOP = 1; private static final int ANCHOR_LEFT = 2; private static final int ANCHOR_BOTTOM = 4; private static final int ANCHOR_RIGHT = 8; private static final int DELAY = 15; // Delay between frames private static final int TOTAL_FRAME_COUNT = 7; // Total number of frames in animation sequence private final InternalDecorator myInternalDecorator; private final MyUISettingsListener myUISettingsListener; private WindowInfoImpl myInfo; private final Disposable myDisposable = Disposer.newDisposable(); private final Alarm myDelayAlarm; // Determines moment when tool window should become transparent private final Alarm myFrameTicker; // Determines moments of rendering of next frame private final MyAnimator myAnimator; // Renders alpha ratio private int myCurrentFrame; // current frame in transparency animation private float myStartRatio; private float myEndRatio; // start and end alpha ratio for transparency animation FloatingDecorator(final IdeFrameImpl owner, final WindowInfoImpl info, final InternalDecorator internalDecorator) { super(owner, internalDecorator.getToolWindow().getId()); MnemonicHelper.init(getContentPane()); myInternalDecorator = internalDecorator; setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); final JComponent cp = (JComponent)getContentPane(); cp.setLayout(new BorderLayout()); if (SystemInfo.isWindows) { setUndecorated(true); cp.add(new BorderItem(ANCHOR_TOP), BorderLayout.NORTH); cp.add(new BorderItem(ANCHOR_LEFT), BorderLayout.WEST); cp.add(new BorderItem(ANCHOR_BOTTOM), BorderLayout.SOUTH); cp.add(new BorderItem(ANCHOR_RIGHT), BorderLayout.EAST); cp.add(myInternalDecorator, BorderLayout.CENTER); } else { // Due to JDK's bug #4234645 we cannot support custom decoration on Linux platform. // The prblem is that Window.setLocation() doesn't work properly wjen the dialod is displayable. // Therefore we use native WM decoration. // TODO[vova] investigate the problem under Mac OSX. cp.add(myInternalDecorator, BorderLayout.CENTER); getRootPane().putClientProperty("Window.style", "small"); } setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new MyWindowListener()); // myDelayAlarm = new Alarm(); myFrameTicker = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myDisposable); myAnimator = new MyAnimator(); myCurrentFrame = 0; myStartRatio = 0.0f; myEndRatio = 0.0f; myUISettingsListener = new MyUISettingsListener(); // IdeGlassPaneImpl ideGlassPane = new IdeGlassPaneImpl(getRootPane()); getRootPane().setGlassPane(ideGlassPane); //workaround: we need to add this IdeGlassPane instance as dispatcher in IdeEventQueue ideGlassPane.addMousePreprocessor(new MouseAdapter() { }, myDisposable); apply(info); } public final void show() { setFocusableWindowState(myInfo.isActive()); super.show(); final UISettings uiSettings = UISettings.getInstance(); if (uiSettings.ENABLE_ALPHA_MODE) { final WindowManagerEx windowManager = WindowManagerEx.getInstanceEx(); windowManager.setAlphaModeEnabled(this, true); if (myInfo.isActive()) { windowManager.setAlphaModeRatio(this, 0.0f); } else { windowManager.setAlphaModeRatio(this, uiSettings.ALPHA_MODE_RATIO); } } paint(getGraphics()); // This prevents annoying flick setFocusableWindowState(true); uiSettings.addUISettingsListener(myUISettingsListener, myDelayAlarm); } public final void dispose() { if (ScreenUtil.isStandardAddRemoveNotify(getParent())) { Disposer.dispose(myDelayAlarm); Disposer.dispose(myDisposable); } else { if (isShowing()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { show(); } }); } } super.dispose(); } final void apply(final WindowInfoImpl info) { LOG.assertTrue(info.isFloating()); myInfo = info; // Set alpha mode final UISettings uiSettings = UISettings.getInstance(); if (uiSettings.ENABLE_ALPHA_MODE && isShowing() && isDisplayable()) { myDelayAlarm.cancelAllRequests(); if (myInfo.isActive()) { // make window non transparent myFrameTicker.cancelAllRequests(); myStartRatio = getCurrentAlphaRatio(); if (myCurrentFrame > 0) { myCurrentFrame = TOTAL_FRAME_COUNT - myCurrentFrame; } myEndRatio = .0f; myFrameTicker.addRequest(myAnimator, DELAY); } else { // make window transparent myDelayAlarm.addRequest(new Runnable() { public void run() { myFrameTicker.cancelAllRequests(); myStartRatio = getCurrentAlphaRatio(); if (myCurrentFrame > 0) { myCurrentFrame = TOTAL_FRAME_COUNT - myCurrentFrame; } myEndRatio = uiSettings.ALPHA_MODE_RATIO; myFrameTicker.addRequest(myAnimator, DELAY); } }, uiSettings.ALPHA_MODE_DELAY); } } } private float getCurrentAlphaRatio() { float delta = (myEndRatio - myStartRatio) / (float)TOTAL_FRAME_COUNT; if (myStartRatio > myEndRatio) { // dialog is becoming non transparent quicker delta *= 2; } final float ratio = myStartRatio + (float)myCurrentFrame * delta; return Math.min(1.0f, Math.max(.0f, ratio)); } private final class BorderItem extends JPanel { private static final int RESIZER_WIDTH = 10; private final int myAnchor; private int myMotionMask; private Point myLastPoint; private boolean myDragging; public BorderItem(final int anchor) { myAnchor = anchor; enableEvents(MouseEvent.MOUSE_EVENT_MASK | MouseEvent.MOUSE_MOTION_EVENT_MASK); } protected final void processMouseMotionEvent(final MouseEvent e) { super.processMouseMotionEvent(e); if (MouseEvent.MOUSE_DRAGGED == e.getID() && myLastPoint != null) { final Point newPoint = e.getPoint(); SwingUtilities.convertPointToScreen(newPoint, this); final Rectangle screenBounds = WindowManagerEx.getInstanceEx().getScreenBounds(); newPoint.x = Math.min(Math.max(newPoint.x, screenBounds.x), screenBounds.width); newPoint.y = Math.min(Math.max(newPoint.y, screenBounds.y), screenBounds.height); final Rectangle oldBounds = FloatingDecorator.this.getBounds(); final Rectangle newBounds = new Rectangle(oldBounds); if ((myMotionMask & ANCHOR_TOP) > 0) { newPoint.y = Math.min(newPoint.y, oldBounds.y + oldBounds.height - 2 * DIVIDER_WIDTH); if (newPoint.y < screenBounds.y + DIVIDER_WIDTH) { newPoint.y = screenBounds.y; } final Point offset = new Point(newPoint.x - myLastPoint.x, newPoint.y - myLastPoint.y); newBounds.y = oldBounds.y + offset.y; newBounds.height = oldBounds.height - offset.y; } if ((myMotionMask & ANCHOR_LEFT) > 0) { newPoint.x = Math.min(newPoint.x, oldBounds.x + oldBounds.width - 2 * DIVIDER_WIDTH); if (newPoint.x < screenBounds.x + DIVIDER_WIDTH) { newPoint.x = screenBounds.x; } final Point offset = new Point(newPoint.x - myLastPoint.x, newPoint.y - myLastPoint.y); newBounds.x = oldBounds.x + offset.x; newBounds.width = oldBounds.width - offset.x; } if ((myMotionMask & ANCHOR_BOTTOM) > 0) { newPoint.y = Math.max(newPoint.y, oldBounds.y + 2 * DIVIDER_WIDTH); if (newPoint.y > screenBounds.height - DIVIDER_WIDTH) { newPoint.y = screenBounds.height; } final Point offset = new Point(newPoint.x - myLastPoint.x, newPoint.y - myLastPoint.y); newBounds.height = oldBounds.height + offset.y; } if ((myMotionMask & ANCHOR_RIGHT) > 0) { newPoint.x = Math.max(newPoint.x, oldBounds.x + 2 * DIVIDER_WIDTH); if (newPoint.x > screenBounds.width - DIVIDER_WIDTH) { newPoint.x = screenBounds.width; } final Point offset = new Point(newPoint.x - myLastPoint.x, newPoint.y - myLastPoint.y); newBounds.width = oldBounds.width + offset.x; } // It's much better to resize frame this way then via Component.setBounds() method. // Component.setBounds() method cause annoying repainting and blinking. //FloatingDecorator.this.getPeer().setBounds(newBounds.x,newBounds.y,newBounds.width,newBounds.height, 0); FloatingDecorator.this.setBounds(newBounds.x, newBounds.y, newBounds.width, newBounds.height); myLastPoint = newPoint; } else if (e.getID() == MouseEvent.MOUSE_MOVED) { if (!myDragging) { setMotionMask(e.getPoint()); } } } protected final void processMouseEvent(final MouseEvent e) { super.processMouseEvent(e); switch (e.getID()) { case MouseEvent.MOUSE_PRESSED: { myLastPoint = e.getPoint(); SwingUtilities.convertPointToScreen(myLastPoint, this); setMotionMask(e.getPoint()); myDragging = true; break; } case MouseEvent.MOUSE_RELEASED: { FloatingDecorator.this.validate(); FloatingDecorator.this.repaint(); myDragging = false; break; } case MouseEvent.MOUSE_ENTERED: { if (!myDragging) { setMotionMask(e.getPoint()); } } } } private void setMotionMask(final Point p) { myMotionMask = myAnchor; if (ANCHOR_TOP == myAnchor || ANCHOR_BOTTOM == myAnchor) { if (p.getX() < RESIZER_WIDTH) { myMotionMask |= ANCHOR_LEFT; } else if (p.getX() > getWidth() - RESIZER_WIDTH) { myMotionMask |= ANCHOR_RIGHT; } } else { if (p.getY() < RESIZER_WIDTH) { myMotionMask |= ANCHOR_TOP; } else if (p.getY() > getHeight() - RESIZER_WIDTH) { myMotionMask |= ANCHOR_BOTTOM; } } if (myMotionMask == ANCHOR_TOP) { setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)); } else if (myMotionMask == (ANCHOR_TOP | ANCHOR_LEFT)) { setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)); } else if (myMotionMask == ANCHOR_LEFT) { setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)); } else if (myMotionMask == (ANCHOR_LEFT | ANCHOR_BOTTOM)) { setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR)); } else if (myMotionMask == ANCHOR_BOTTOM) { setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); } else if (myMotionMask == (ANCHOR_BOTTOM | ANCHOR_RIGHT)) { setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); } else if (myMotionMask == ANCHOR_RIGHT) { setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); } else if (myMotionMask == (ANCHOR_RIGHT | ANCHOR_TOP)) { setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)); } } public final Dimension getPreferredSize() { final Dimension d = super.getPreferredSize(); if (ANCHOR_TOP == myAnchor || ANCHOR_BOTTOM == myAnchor) { d.height = DIVIDER_WIDTH; } else { d.width = DIVIDER_WIDTH; } return d; } public final void paint(final Graphics g) { super.paint(g); final JBColor lightGray = new JBColor(Color.lightGray, Gray._95); final JBColor gray = new JBColor(Color.gray, Gray._95); if (ANCHOR_TOP == myAnchor) { g.setColor(lightGray); UIUtil.drawLine(g, 0, 0, getWidth() - 1, 0); UIUtil.drawLine(g, 0, 0, 0, getHeight() - 1); g.setColor(JBColor.GRAY); UIUtil.drawLine(g, getWidth() - 1, 0, getWidth() - 1, getHeight() - 1); } else if (ANCHOR_LEFT == myAnchor) { g.setColor(lightGray); UIUtil.drawLine(g, 0, 0, 0, getHeight() - 1); } else { if (ANCHOR_BOTTOM == myAnchor) { g.setColor(lightGray); UIUtil.drawLine(g, 0, 0, 0, getHeight() - 1); g.setColor(gray); UIUtil.drawLine(g, 0, getHeight() - 1, getWidth() - 1, getHeight() - 1); UIUtil.drawLine(g, getWidth() - 1, 0, getWidth() - 1, getHeight() - 1); } else { // RIGHT g.setColor(gray); UIUtil.drawLine(g, getWidth() - 1, 0, getWidth() - 1, getHeight() - 1); } } } } private final class MyWindowListener extends WindowAdapter { public void windowClosing(final WindowEvent e) { myInternalDecorator.fireResized(); myInternalDecorator.fireHidden(); } } private final class MyAnimator implements Runnable { public final void run() { final WindowManagerEx windowManager = WindowManagerEx.getInstanceEx(); if (isDisplayable() && isShowing()) { windowManager.setAlphaModeRatio(FloatingDecorator.this, getCurrentAlphaRatio()); } if (myCurrentFrame < TOTAL_FRAME_COUNT) { myCurrentFrame++; myFrameTicker.addRequest(myAnimator, DELAY); } else { myFrameTicker.cancelAllRequests(); } } } private final class MyUISettingsListener implements UISettingsListener { public void uiSettingsChanged(final UISettings uiSettings) { LOG.assertTrue(isDisplayable()); LOG.assertTrue(isShowing()); final WindowManagerEx windowManager = WindowManagerEx.getInstanceEx(); myDelayAlarm.cancelAllRequests(); if (uiSettings.ENABLE_ALPHA_MODE) { if (!myInfo.isActive()) { windowManager.setAlphaModeEnabled(FloatingDecorator.this, true); windowManager.setAlphaModeRatio(FloatingDecorator.this, uiSettings.ALPHA_MODE_RATIO); } } else { windowManager.setAlphaModeEnabled(FloatingDecorator.this, false); } } } }