blob: 07b8feeaab203aee78e67bceb7390f366777b494 [file] [log] [blame]
/*
* 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.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.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");
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 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());
new MnemonicHelper().register(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.SHARED_THREAD);
myAnimator=new MyAnimator();
myCurrentFrame=0;
myStartRatio=0.0f;
myEndRatio=0.0f;
myUISettingsListener=new MyUISettingsListener();
//
getRootPane().setGlassPane(new IdeGlassPaneImpl(getRootPane()));
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);
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 DIVIDER_WIDTH=3;
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);
}
}
}
}