blob: 83ddb7e6555127770299f8eec6b060260e467f08 [file] [log] [blame]
/*
* 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.openapi.wm.impl;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.ui.ThreeComponentsSplitter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowType;
import com.intellij.openapi.wm.ex.ToolWindowEx;
import com.intellij.openapi.wm.impl.commands.FinalizableCommand;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.components.JBLayeredPane;
import com.intellij.util.containers.HashMap;
import com.intellij.util.ui.FadeInFadeOut;
import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Comparator;
/**
* This panel contains all tool stripes and JLayeredPanle at the center area. All tool windows are
* located inside this layered pane.
*
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public final class ToolWindowsPane extends JBLayeredPane implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.ToolWindowsPane");
private final IdeFrameImpl myFrame;
private final HashMap<String, StripeButton> myId2Button;
private final HashMap<String, InternalDecorator> myId2Decorator;
private final HashMap<StripeButton, WindowInfoImpl> myButton2Info;
private final HashMap<InternalDecorator, WindowInfoImpl> myDecorator2Info;
private final HashMap<String, Float> myId2SplitProportion;
/**
* This panel is the layered pane where all sliding tool windows are located. The DEFAULT
* layer contains splitters. The PALETTE layer contains all sliding tool windows.
*/
private final MyLayeredPane myLayeredPane;
/*
* Splitters.
*/
private final ThreeComponentsSplitter myVerticalSplitter;
private final ThreeComponentsSplitter myHorizontalSplitter;
/*
* Tool stripes.
*/
private final Stripe myLeftStripe;
private final Stripe myRightStripe;
private final Stripe myBottomStripe;
private final Stripe myTopStripe;
private final ArrayList<Stripe> myStripes = new ArrayList<Stripe>();
private final MyUISettingsListenerImpl myUISettingsListener;
private final ToolWindowManagerImpl myManager;
private boolean myStripesOverlayed;
private final Disposable myDisposable = Disposer.newDisposable();
private boolean myWidescreen = false;
private boolean myLeftHorizontalSplit = false;
private boolean myRightHorizontalSplit = false;
ToolWindowsPane(final IdeFrameImpl frame, ToolWindowManagerImpl manager) {
myManager = manager;
setOpaque(false);
myFrame = frame;
myId2Button = new HashMap<String, StripeButton>();
myId2Decorator = new HashMap<String, InternalDecorator>();
myButton2Info = new HashMap<StripeButton, WindowInfoImpl>();
myDecorator2Info = new HashMap<InternalDecorator, WindowInfoImpl>();
myUISettingsListener = new MyUISettingsListenerImpl();
myId2SplitProportion = new HashMap<String, Float>();
// Splitters
myVerticalSplitter = new ThreeComponentsSplitter(true);
Disposer.register(this, myVerticalSplitter);
myVerticalSplitter.setDividerWidth(0);
myVerticalSplitter.setDividerMouseZoneSize(Registry.intValue("ide.splitter.mouseZone"));
myVerticalSplitter.setBackground(Color.gray);
myHorizontalSplitter = new ThreeComponentsSplitter(false);
Disposer.register(this, myHorizontalSplitter);
myHorizontalSplitter.setDividerWidth(0);
myHorizontalSplitter.setDividerMouseZoneSize(Registry.intValue("ide.splitter.mouseZone"));
myHorizontalSplitter.setBackground(Color.gray);
myWidescreen = UISettings.getInstance().WIDESCREEN_SUPPORT;
myLeftHorizontalSplit = UISettings.getInstance().LEFT_HORIZONTAL_SPLIT;
myRightHorizontalSplit = UISettings.getInstance().RIGHT_HORIZONTAL_SPLIT;
if (myWidescreen) {
myHorizontalSplitter.setInnerComponent(myVerticalSplitter);
}
else {
myVerticalSplitter.setInnerComponent(myHorizontalSplitter);
}
// Tool stripes
myTopStripe = new Stripe(SwingConstants.TOP, manager);
myStripes.add(myTopStripe);
myLeftStripe = new Stripe(SwingConstants.LEFT, manager);
myStripes.add(myLeftStripe);
myBottomStripe = new Stripe(SwingConstants.BOTTOM, manager);
myStripes.add(myBottomStripe);
myRightStripe = new Stripe(SwingConstants.RIGHT, manager);
myStripes.add(myRightStripe);
updateToolStripesVisibility();
// Layered pane
myLayeredPane = new MyLayeredPane(myWidescreen ? myHorizontalSplitter : myVerticalSplitter);
// Compose layout
add(myTopStripe, JLayeredPane.POPUP_LAYER);
add(myLeftStripe, JLayeredPane.POPUP_LAYER);
add(myBottomStripe, JLayeredPane.POPUP_LAYER);
add(myRightStripe, JLayeredPane.POPUP_LAYER);
add(myLayeredPane, JLayeredPane.DEFAULT_LAYER);
}
@Override
public void doLayout() {
Dimension size = getSize();
if (!myTopStripe.isVisible()) {
myTopStripe.setBounds(0, 0, 0, 0);
myBottomStripe.setBounds(0, 0, 0, 0);
myLeftStripe.setBounds(0, 0, 0, 0);
myRightStripe.setBounds(0, 0, 0, 0);
myLayeredPane.setBounds(0, 0, getWidth(), getHeight());
}
else {
Dimension topSize = myTopStripe.getPreferredSize();
Dimension bottomSize = myBottomStripe.getPreferredSize();
Dimension leftSize = myLeftStripe.getPreferredSize();
Dimension rightSize = myRightStripe.getPreferredSize();
myTopStripe.setBounds(0, 0, size.width, topSize.height);
myLeftStripe.setBounds(0, topSize.height, leftSize.width, size.height - topSize.height - bottomSize.height);
myRightStripe
.setBounds(size.width - rightSize.width, topSize.height, rightSize.width, size.height - topSize.height - bottomSize.height);
myBottomStripe.setBounds(0, size.height - bottomSize.height, size.width, bottomSize.height);
if (UISettings.getInstance().HIDE_TOOL_STRIPES || UISettings.getInstance().PRESENTATION_MODE) {
myLayeredPane.setBounds(0, 0, size.width, size.height);
}
else {
myLayeredPane.setBounds(leftSize.width, topSize.height, size.width - leftSize.width - rightSize.width,
size.height - topSize.height - bottomSize.height);
}
}
}
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
}
/**
* Invoked when enclosed frame is being shown.
*/
public final void addNotify() {
super.addNotify();
if (ScreenUtil.isStandardAddRemoveNotify(this)) {
UISettings.getInstance().addUISettingsListener(myUISettingsListener, myDisposable);
}
}
/**
* Invoked when enclosed frame is being disposed.
*/
public final void removeNotify() {
if (ScreenUtil.isStandardAddRemoveNotify(this)) {
Disposer.dispose(myDisposable);
}
super.removeNotify();
}
public Project getProject() {
return myFrame.getProject();
}
/**
* Creates command which adds button into the specified tool stripe.
* Command uses copy of passed <code>info</code> object.
*
* @param button button which should be added.
* @param info window info for the corresponded tool window.
* @param comparator which is used to sort buttons within the stripe.
* @param finishCallBack invoked when the command is completed.
*/
final FinalizableCommand createAddButtonCmd(final StripeButton button,
final WindowInfoImpl info,
final Comparator<StripeButton> comparator,
final Runnable finishCallBack) {
final WindowInfoImpl copiedInfo = info.copy();
myId2Button.put(copiedInfo.getId(), button);
myButton2Info.put(button, copiedInfo);
return new AddToolStripeButtonCmd(button, copiedInfo, comparator, finishCallBack);
}
/**
* Creates command which shows tool window with specified set of parameters.
* Command uses cloned copy of passed <code>info</code> object.
*
* @param dirtyMode if <code>true</code> then JRootPane will not be validated and repainted after adding
* the decorator. Moreover in this (dirty) mode animation doesn't work.
*/
final FinalizableCommand createAddDecoratorCmd(
final InternalDecorator decorator,
final WindowInfoImpl info,
final boolean dirtyMode,
final Runnable finishCallBack
) {
final WindowInfoImpl copiedInfo = info.copy();
final String id = copiedInfo.getId();
myDecorator2Info.put(decorator, copiedInfo);
myId2Decorator.put(id, decorator);
if (info.isDocked()) {
WindowInfoImpl sideInfo = getDockedInfoAt(info.getAnchor(), !info.isSplit());
if (sideInfo == null) {
return new AddDockedComponentCmd(decorator, info, dirtyMode, finishCallBack);
}
else {
return new AddAndSplitDockedComponentCmd(decorator, info, dirtyMode, finishCallBack);
}
}
else if (info.isSliding()) {
return new AddSlidingComponentCmd(decorator, info, dirtyMode, finishCallBack);
}
else {
throw new IllegalArgumentException("Unknown window type: " + info.getType());
}
}
/**
* Creates command which removes tool button from tool stripe.
*
* @param id <code>ID</code> of the button to be removed.
*/
final FinalizableCommand createRemoveButtonCmd(final String id, final Runnable finishCallBack) {
final StripeButton button = getButtonById(id);
final WindowInfoImpl info = getButtonInfoById(id);
//
myButton2Info.remove(button);
myId2Button.remove(id);
return new RemoveToolStripeButtonCmd(button, info, finishCallBack);
}
/**
* Creates command which hides tool window with specified set of parameters.
*
* @param dirtyMode if <code>true</code> then JRootPane will not be validated and repainted after removing
* the decorator. Moreover in this (dirty) mode animation doesn't work.
*/
final FinalizableCommand createRemoveDecoratorCmd(final String id, final boolean dirtyMode, final Runnable finishCallBack) {
final Component decorator = getDecoratorById(id);
final WindowInfoImpl info = getDecoratorInfoById(id);
myDecorator2Info.remove(decorator);
myId2Decorator.remove(id);
WindowInfoImpl sideInfo = getDockedInfoAt(info.getAnchor(), !info.isSplit());
if (info.isDocked()) {
if (sideInfo == null) {
return new RemoveDockedComponentCmd(info, dirtyMode, finishCallBack);
}
else {
return new RemoveSplitAndDockedComponentCmd(info, dirtyMode, finishCallBack);
}
}
else if (info.isSliding()) {
return new RemoveSlidingComponentCmd(decorator, info, dirtyMode, finishCallBack);
}
else {
throw new IllegalArgumentException("Unknown window type");
}
}
/**
* Creates command which sets specified document component.
*
* @param component component to be set.
*/
final FinalizableCommand createSetEditorComponentCmd(final JComponent component, final Runnable finishCallBack) {
return new SetEditorComponentCmd(component, finishCallBack);
}
final FinalizableCommand createUpdateButtonPositionCmd(String id, final Runnable finishCallback) {
return new UpdateButtonPositionCmd(id, finishCallback);
}
final JComponent getMyLayeredPane() {
return myLayeredPane;
}
private StripeButton getButtonById(final String id) {
return myId2Button.get(id);
}
private Component getDecoratorById(final String id) {
return myId2Decorator.get(id);
}
/**
* @param id <code>ID</code> of tool stripe butoon.
* @return <code>WindowInfo</code> associated with specified tool stripe button.
*/
private WindowInfoImpl getButtonInfoById(final String id) {
return myButton2Info.get(myId2Button.get(id));
}
/**
* @param id <code>ID</code> of decorator.
* @return <code>WindowInfo</code> associated with specified window decorator.
*/
private WindowInfoImpl getDecoratorInfoById(final String id) {
return myDecorator2Info.get(myId2Decorator.get(id));
}
/**
* Sets (docks) specified component to the specified anchor.
*/
private void setComponent(final JComponent component, final ToolWindowAnchor anchor, final float weight) {
if (ToolWindowAnchor.TOP == anchor) {
myVerticalSplitter.setFirstComponent(component);
myVerticalSplitter.setFirstSize((int)(myLayeredPane.getHeight() * weight));
}
else if (ToolWindowAnchor.LEFT == anchor) {
myHorizontalSplitter.setFirstComponent(component);
myHorizontalSplitter.setFirstSize((int)(myLayeredPane.getWidth() * weight));
}
else if (ToolWindowAnchor.BOTTOM == anchor) {
myVerticalSplitter.setLastComponent(component);
myVerticalSplitter.setLastSize((int)(myLayeredPane.getHeight() * weight));
}
else if (ToolWindowAnchor.RIGHT == anchor) {
myHorizontalSplitter.setLastComponent(component);
myHorizontalSplitter.setLastSize((int)(myLayeredPane.getWidth() * weight));
}
else {
LOG.error("unknown anchor: " + anchor);
}
}
private JComponent getComponentAt(ToolWindowAnchor anchor) {
if (ToolWindowAnchor.TOP == anchor) {
return myVerticalSplitter.getFirstComponent();
}
else if (ToolWindowAnchor.LEFT == anchor) {
return myHorizontalSplitter.getFirstComponent();
}
else if (ToolWindowAnchor.BOTTOM == anchor) {
return myVerticalSplitter.getLastComponent();
}
else if (ToolWindowAnchor.RIGHT == anchor) {
return myHorizontalSplitter.getLastComponent();
}
else {
LOG.error("unknown anchor: " + anchor);
return null;
}
}
private float getPreferredSplitProportion(String id, float defaultValue) {
Float f = myId2SplitProportion.get(id);
return (f == null ? defaultValue : f);
}
private WindowInfoImpl getDockedInfoAt(ToolWindowAnchor anchor, boolean side) {
for (WindowInfoImpl info : myDecorator2Info.values()) {
if (info.isVisible() && info.isDocked() && info.getAnchor() == anchor && side == info.isSplit()) {
return info;
}
}
return null;
}
public void setDocumentComponent(final JComponent component) {
(myWidescreen ? myVerticalSplitter : myHorizontalSplitter).setInnerComponent(component);
}
private void updateToolStripesVisibility() {
boolean oldVisible = myLeftStripe.isVisible();
final boolean showButtons = !UISettings.getInstance().HIDE_TOOL_STRIPES && !UISettings.getInstance().PRESENTATION_MODE;
boolean visible = showButtons || myStripesOverlayed;
myLeftStripe.setVisible(visible);
myRightStripe.setVisible(visible);
myTopStripe.setVisible(visible);
myBottomStripe.setVisible(visible);
boolean overlayed = !showButtons && myStripesOverlayed;
myLeftStripe.setOverlayed(overlayed);
myRightStripe.setOverlayed(overlayed);
myTopStripe.setOverlayed(overlayed);
myBottomStripe.setOverlayed(overlayed);
if (oldVisible != visible) {
revalidate();
repaint();
}
}
Stripe getStripeFor(String id) {
final ToolWindowAnchor anchor = myManager.getToolWindow(id).getAnchor();
if (ToolWindowAnchor.TOP == anchor) {
return myTopStripe;
}
else if (ToolWindowAnchor.BOTTOM == anchor) {
return myBottomStripe;
}
else if (ToolWindowAnchor.LEFT == anchor) {
return myLeftStripe;
}
else if (ToolWindowAnchor.RIGHT == anchor) {
return myRightStripe;
}
throw new IllegalArgumentException("Anchor=" + anchor);
}
Stripe getStripeFor(final Rectangle screenRec, Stripe preferred) {
if (preferred.containsScreen(screenRec)) {
return myStripes.get(myStripes.indexOf(preferred));
}
for (Stripe each : myStripes) {
if (each.containsScreen(screenRec)) {
return myStripes.get(myStripes.indexOf(each));
}
}
return null;
}
void startDrag() {
for (Stripe each : myStripes) {
each.startDrag();
}
}
void stopDrag() {
for (Stripe each : myStripes) {
each.stopDrag();
}
}
public void stretchWidth(ToolWindow wnd, int value) {
stretch(wnd, value);
}
public void stretchHeight(ToolWindow wnd, int value) {
stretch(wnd, value);
}
private void stretch(ToolWindow wnd, int value) {
if (!wnd.isVisible()) return;
Resizer resizer = null;
Component cmp = null;
if (wnd.getType() == ToolWindowType.DOCKED) {
cmp = getComponentAt(wnd.getAnchor());
if (cmp != null) {
if (wnd.getAnchor().isHorizontal()) {
resizer = myVerticalSplitter.getFirstComponent() == cmp
? new Resizer.Splitter.FirstComponent(myVerticalSplitter)
: new Resizer.Splitter.LastComponent(myVerticalSplitter);
}
else {
resizer = myHorizontalSplitter.getFirstComponent() == cmp
? new Resizer.Splitter.FirstComponent(myHorizontalSplitter)
: new Resizer.Splitter.LastComponent(myHorizontalSplitter);
}
}
}
else if (wnd.getType() == ToolWindowType.SLIDING) {
cmp = wnd.getComponent();
while (cmp != null) {
if (cmp.getParent() == myLayeredPane) break;
cmp = cmp.getParent();
}
if (cmp != null) {
if (wnd.getAnchor() == ToolWindowAnchor.TOP) {
resizer = new Resizer.LayeredPane.Top(cmp);
}
else if (wnd.getAnchor() == ToolWindowAnchor.BOTTOM) {
resizer = new Resizer.LayeredPane.Bottom(cmp);
}
else if (wnd.getAnchor() == ToolWindowAnchor.LEFT) {
resizer = new Resizer.LayeredPane.Left(cmp);
}
else if (wnd.getAnchor() == ToolWindowAnchor.RIGHT) {
resizer = new Resizer.LayeredPane.Right(cmp);
}
}
}
if (resizer == null) return;
int currentValue = wnd.getAnchor().isHorizontal() ? cmp.getHeight() : cmp.getWidth();
int actualSize = currentValue + value;
int minValue =
wnd.getAnchor().isHorizontal() ? ((ToolWindowEx)wnd).getDecorator().getHeaderHeight() : 16 + myHorizontalSplitter.getDividerWidth();
int maxValue = wnd.getAnchor().isHorizontal() ? myLayeredPane.getHeight() : myLayeredPane.getWidth();
if (actualSize < minValue) {
actualSize = minValue;
}
if (actualSize > maxValue) {
actualSize = maxValue;
}
resizer.setSize(actualSize);
}
private void updateLayout() {
if (myWidescreen != UISettings.getInstance().WIDESCREEN_SUPPORT) {
JComponent documentComponent = (myWidescreen ? myVerticalSplitter : myHorizontalSplitter).getInnerComponent();
myWidescreen = UISettings.getInstance().WIDESCREEN_SUPPORT;
if (myWidescreen) {
myVerticalSplitter.setInnerComponent(null);
myHorizontalSplitter.setInnerComponent(myVerticalSplitter);
}
else {
myHorizontalSplitter.setInnerComponent(null);
myVerticalSplitter.setInnerComponent(myHorizontalSplitter);
}
myLayeredPane.remove(myWidescreen ? myVerticalSplitter : myHorizontalSplitter);
myLayeredPane.add(myWidescreen ? myHorizontalSplitter : myVerticalSplitter, DEFAULT_LAYER);
setDocumentComponent(documentComponent);
}
if (myLeftHorizontalSplit != UISettings.getInstance().LEFT_HORIZONTAL_SPLIT) {
JComponent component = getComponentAt(ToolWindowAnchor.LEFT);
if (component instanceof Splitter) {
Splitter splitter = (Splitter)component;
InternalDecorator first = (InternalDecorator)splitter.getFirstComponent();
InternalDecorator second = (InternalDecorator)splitter.getSecondComponent();
setComponent(splitter, ToolWindowAnchor.LEFT, ToolWindowAnchor.LEFT.isSplitVertically()
? first.getWindowInfo().getWeight()
: first.getWindowInfo().getWeight() + second.getWindowInfo().getWeight());
}
myLeftHorizontalSplit = UISettings.getInstance().LEFT_HORIZONTAL_SPLIT;
}
if (myRightHorizontalSplit != UISettings.getInstance().RIGHT_HORIZONTAL_SPLIT) {
JComponent component = getComponentAt(ToolWindowAnchor.RIGHT);
if (component instanceof Splitter) {
Splitter splitter = (Splitter)component;
InternalDecorator first = (InternalDecorator)splitter.getFirstComponent();
InternalDecorator second = (InternalDecorator)splitter.getSecondComponent();
setComponent(splitter, ToolWindowAnchor.RIGHT, ToolWindowAnchor.RIGHT.isSplitVertically()
? first.getWindowInfo().getWeight()
: first.getWindowInfo().getWeight() + second.getWindowInfo().getWeight());
}
myRightHorizontalSplit = UISettings.getInstance().RIGHT_HORIZONTAL_SPLIT;
}
}
interface Resizer {
void setSize(int size);
abstract class Splitter implements Resizer {
ThreeComponentsSplitter mySplitter;
Splitter(ThreeComponentsSplitter splitter) {
mySplitter = splitter;
}
static class FirstComponent extends Splitter {
FirstComponent(ThreeComponentsSplitter splitter) {
super(splitter);
}
public void setSize(int size) {
mySplitter.setFirstSize(size);
}
}
static class LastComponent extends Splitter {
LastComponent(ThreeComponentsSplitter splitter) {
super(splitter);
}
public void setSize(int size) {
mySplitter.setLastSize(size);
}
}
}
abstract class LayeredPane implements Resizer {
Component myComponent;
protected LayeredPane(Component component) {
myComponent = component;
}
public final void setSize(int size) {
_setSize(size);
if (myComponent.getParent() instanceof JComponent) {
JComponent parent = (JComponent)myComponent;
parent.revalidate();
parent.repaint();
}
}
abstract void _setSize(int size);
static class Left extends LayeredPane {
Left(Component component) {
super(component);
}
public void _setSize(int size) {
myComponent.setSize(size, myComponent.getHeight());
}
}
static class Right extends LayeredPane {
Right(Component component) {
super(component);
}
public void _setSize(int size) {
Rectangle bounds = myComponent.getBounds();
int delta = size - bounds.width;
bounds.x -= delta;
bounds.width += delta;
myComponent.setBounds(bounds);
}
}
static class Top extends LayeredPane {
Top(Component component) {
super(component);
}
public void _setSize(int size) {
myComponent.setSize(myComponent.getWidth(), size);
}
}
static class Bottom extends LayeredPane {
Bottom(Component component) {
super(component);
}
public void _setSize(int size) {
Rectangle bounds = myComponent.getBounds();
int delta = size - bounds.height;
bounds.y -= delta;
bounds.height += delta;
myComponent.setBounds(bounds);
}
}
}
}
private final class AddDockedComponentCmd extends FinalizableCommand {
private final JComponent myComponent;
private final WindowInfoImpl myInfo;
private final boolean myDirtyMode;
public AddDockedComponentCmd(final JComponent component,
final WindowInfoImpl info,
final boolean dirtyMode,
final Runnable finishCallBack) {
super(finishCallBack);
myComponent = component;
myInfo = info;
myDirtyMode = dirtyMode;
}
public final void run() {
try {
final ToolWindowAnchor anchor = myInfo.getAnchor();
setComponent(myComponent, anchor, normalizeWeigh(myInfo.getWeight()));
if (!myDirtyMode) {
myLayeredPane.validate();
myLayeredPane.repaint();
}
}
finally {
finish();
}
}
}
private final class AddAndSplitDockedComponentCmd extends FinalizableCommand {
private final JComponent myNewComponent;
private final WindowInfoImpl myInfo;
private final boolean myDirtyMode;
private AddAndSplitDockedComponentCmd(final JComponent newComponent,
final WindowInfoImpl info, final boolean dirtyMode, final Runnable finishCallBack) {
super(finishCallBack);
myNewComponent = newComponent;
myInfo = info;
myDirtyMode = dirtyMode;
}
public void run() {
try {
float newWeight;
final ToolWindowAnchor anchor = myInfo.getAnchor();
final Disposable splitterDisposable = new Disposable() {
@Override
public void dispose() {
}
};
Disposer.register(myDisposable, splitterDisposable);
final Splitter splitter = new Splitter(anchor.isSplitVertically()) {
@Override
public void removeNotify() {
super.removeNotify();
Disposer.dispose(splitterDisposable);
}
};
if (!anchor.isHorizontal()) {
UISettings.getInstance().addUISettingsListener(new UISettingsListener() {
@Override
public void uiSettingsChanged(UISettings source) {
if (anchor == ToolWindowAnchor.LEFT) {
splitter.setOrientation(!source.LEFT_HORIZONTAL_SPLIT);
}
if (anchor == ToolWindowAnchor.RIGHT) {
splitter.setOrientation(!source.RIGHT_HORIZONTAL_SPLIT);
}
}
}, splitterDisposable);
splitter.setAllowSwitchOrientationByMouseClick(true);
splitter.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!Splitter.PROP_ORIENTATION.equals(evt.getPropertyName())) return;
boolean isSplitterHorizontalNow = !splitter.isVertical();
UISettings settings = UISettings.getInstance();
if (anchor == ToolWindowAnchor.LEFT) {
if (settings.LEFT_HORIZONTAL_SPLIT != isSplitterHorizontalNow) {
settings.LEFT_HORIZONTAL_SPLIT = isSplitterHorizontalNow;
settings.fireUISettingsChanged();
}
}
if (anchor == ToolWindowAnchor.RIGHT) {
if (settings.RIGHT_HORIZONTAL_SPLIT != isSplitterHorizontalNow) {
settings.RIGHT_HORIZONTAL_SPLIT = isSplitterHorizontalNow;
settings.fireUISettingsChanged();
}
}
}
});
}
InternalDecorator oldComponent = (InternalDecorator)getComponentAt(anchor);
if (myInfo.isSplit()) {
splitter.setFirstComponent(oldComponent);
splitter.setSecondComponent(myNewComponent);
float proportion = getPreferredSplitProportion(oldComponent.getWindowInfo().getId(),
normalizeWeigh(oldComponent.getWindowInfo().getSideWeight() /
(oldComponent.getWindowInfo().getSideWeight() +
myInfo.getSideWeight())));
splitter.setProportion(proportion);
if (!anchor.isHorizontal() && !anchor.isSplitVertically()) {
newWeight = normalizeWeigh(oldComponent.getWindowInfo().getWeight() + myInfo.getWeight());
}
else {
newWeight = normalizeWeigh(oldComponent.getWindowInfo().getWeight());
}
}
else {
splitter.setFirstComponent(myNewComponent);
splitter.setSecondComponent(oldComponent);
splitter.setProportion(normalizeWeigh(myInfo.getSideWeight()));
if (!anchor.isHorizontal() && !anchor.isSplitVertically()) {
newWeight = normalizeWeigh(oldComponent.getWindowInfo().getWeight() + myInfo.getWeight());
}
else {
newWeight = normalizeWeigh(myInfo.getWeight());
}
}
setComponent(splitter, anchor, newWeight);
if (!myDirtyMode) {
myLayeredPane.validate();
myLayeredPane.repaint();
}
}
finally {
finish();
}
}
}
private final class AddSlidingComponentCmd extends FinalizableCommand {
private final Component myComponent;
private final WindowInfoImpl myInfo;
private final boolean myDirtyMode;
public AddSlidingComponentCmd(final Component component,
final WindowInfoImpl info,
final boolean dirtyMode,
final Runnable finishCallBack) {
super(finishCallBack);
myComponent = component;
myInfo = info;
myDirtyMode = dirtyMode;
}
public final void run() {
// Show component.
final UISettings uiSettings = UISettings.getInstance();
if (!myDirtyMode && uiSettings.ANIMATE_WINDOWS && !UISettings.isRemoteDesktopConnected()) {
myLayeredPane.add(myComponent, JLayeredPane.PALETTE_LAYER);
myLayeredPane.moveToFront(myComponent);
myLayeredPane.setBoundsInPaletteLayer(myComponent, myInfo.getAnchor(), myInfo.getWeight());
final FadeInFadeOut fadeIn = new FadeInFadeOut(myComponent, 250, true, myId2Button.get(myInfo.getId()));
add(fadeIn, FadeInFadeOut.LAYER);
fadeIn.setBounds(0, 0, getWidth(), getHeight());
myLayeredPane.remove(myComponent);
fadeIn.doAnimation(new Runnable() {
@Override
public void run() {
remove(fadeIn);
myLayeredPane.add(myComponent, JLayeredPane.PALETTE_LAYER);
//myComponent.requestFocus();
repaint();
finish();
}
});
}
else { // not animated
myLayeredPane.add(myComponent, JLayeredPane.PALETTE_LAYER);
myLayeredPane.setBoundsInPaletteLayer(myComponent, myInfo.getAnchor(), myInfo.getWeight());
if (!myDirtyMode) {
myLayeredPane.revalidate();
myLayeredPane.repaint();
}
finish();
}
}
}
private final class AddToolStripeButtonCmd extends FinalizableCommand {
private final StripeButton myButton;
private final WindowInfoImpl myInfo;
private final Comparator<StripeButton> myComparator;
public AddToolStripeButtonCmd(final StripeButton button,
final WindowInfoImpl info,
final Comparator<StripeButton> comparator,
final Runnable finishCallBack) {
super(finishCallBack);
myButton = button;
myInfo = info;
myComparator = comparator;
}
public final void run() {
try {
final ToolWindowAnchor anchor = myInfo.getAnchor();
if (ToolWindowAnchor.TOP == anchor) {
myTopStripe.addButton(myButton, myComparator);
}
else if (ToolWindowAnchor.LEFT == anchor) {
myLeftStripe.addButton(myButton, myComparator);
}
else if (ToolWindowAnchor.BOTTOM == anchor) {
myBottomStripe.addButton(myButton, myComparator);
}
else if (ToolWindowAnchor.RIGHT == anchor) {
myRightStripe.addButton(myButton, myComparator);
}
else {
LOG.error("unknown anchor: " + anchor);
}
validate();
repaint();
}
finally {
finish();
}
}
}
private final class RemoveToolStripeButtonCmd extends FinalizableCommand {
private final StripeButton myButton;
private final WindowInfoImpl myInfo;
public RemoveToolStripeButtonCmd(final StripeButton button, final WindowInfoImpl info, final Runnable finishCallBack) {
super(finishCallBack);
myButton = button;
myInfo = info;
}
public final void run() {
try {
final ToolWindowAnchor anchor = myInfo.getAnchor();
if (ToolWindowAnchor.TOP == anchor) {
myTopStripe.removeButton(myButton);
}
else if (ToolWindowAnchor.LEFT == anchor) {
myLeftStripe.removeButton(myButton);
}
else if (ToolWindowAnchor.BOTTOM == anchor) {
myBottomStripe.removeButton(myButton);
}
else if (ToolWindowAnchor.RIGHT == anchor) {
myRightStripe.removeButton(myButton);
}
else {
LOG.error("unknown anchor: " + anchor);
}
validate();
repaint();
}
finally {
finish();
}
}
}
private final class RemoveDockedComponentCmd extends FinalizableCommand {
private final WindowInfoImpl myInfo;
private final boolean myDirtyMode;
public RemoveDockedComponentCmd(final WindowInfoImpl info, final boolean dirtyMode, final Runnable finishCallBack) {
super(finishCallBack);
myInfo = info;
myDirtyMode = dirtyMode;
}
public final void run() {
try {
setComponent(null, myInfo.getAnchor(), 0);
if (!myDirtyMode) {
myLayeredPane.validate();
myLayeredPane.repaint();
}
}
finally {
finish();
}
}
}
private final class RemoveSplitAndDockedComponentCmd extends FinalizableCommand {
private final WindowInfoImpl myInfo;
private final boolean myDirtyMode;
private RemoveSplitAndDockedComponentCmd(final WindowInfoImpl info,
boolean dirtyMode,
final Runnable finishCallBack) {
super(finishCallBack);
myInfo = info;
myDirtyMode = dirtyMode;
}
public void run() {
try {
Splitter splitter = (Splitter)getComponentAt(myInfo.getAnchor());
if (myInfo.isSplit()) {
InternalDecorator component = (InternalDecorator)splitter.getFirstComponent();
myId2SplitProportion.put(component.getWindowInfo().getId(), splitter.getProportion());
setComponent(component, myInfo.getAnchor(), component.getWindowInfo().getWeight());
}
else {
InternalDecorator component = (InternalDecorator)splitter.getSecondComponent();
setComponent(component, myInfo.getAnchor(), component.getWindowInfo().getWeight());
}
if (!myDirtyMode) {
myLayeredPane.validate();
myLayeredPane.repaint();
}
}
finally {
finish();
}
}
}
private final class RemoveSlidingComponentCmd extends FinalizableCommand {
private final Component myComponent;
private final WindowInfoImpl myInfo;
private final boolean myDirtyMode;
public RemoveSlidingComponentCmd(Component component, WindowInfoImpl info, boolean dirtyMode, Runnable finishCallBack) {
super(finishCallBack);
myComponent = component;
myInfo = info;
myDirtyMode = dirtyMode;
}
public final void run() {
final UISettings uiSettings = UISettings.getInstance();
if (!myDirtyMode && uiSettings.ANIMATE_WINDOWS && !UISettings.isRemoteDesktopConnected()) {
// Remove component from the layered pane and start animation.
final FadeInFadeOut fadeOut = new FadeInFadeOut(myComponent, 450, false, getButtonById(myInfo.getId()));
add(fadeOut, FadeInFadeOut.LAYER);
fadeOut.setBounds(0, 0, getWidth(), getHeight());
myLayeredPane.remove(myComponent);
fadeOut.doAnimation(new Runnable() {
@Override
public void run() {
remove(fadeOut);
repaint();
finish();
}
});
}
else { // not animated
myLayeredPane.remove(myComponent);
if (!myDirtyMode) {
myLayeredPane.revalidate();
myLayeredPane.repaint();
}
finish();
}
}
}
private final class SetEditorComponentCmd extends FinalizableCommand {
private final JComponent myComponent;
public SetEditorComponentCmd(final JComponent component, final Runnable finishCallBack) {
super(finishCallBack);
myComponent = component;
}
public void run() {
try {
setDocumentComponent(myComponent);
myLayeredPane.validate();
myLayeredPane.repaint();
}
finally {
finish();
}
}
}
private final class UpdateButtonPositionCmd extends FinalizableCommand {
private final String myId;
private UpdateButtonPositionCmd(String id, final Runnable finishCallBack) {
super(finishCallBack);
myId = id;
}
public void run() {
try {
WindowInfoImpl info = getButtonById(myId).getWindowInfo();
ToolWindowAnchor anchor = info.getAnchor();
if (ToolWindowAnchor.TOP == anchor) {
myTopStripe.revalidate();
}
else if (ToolWindowAnchor.LEFT == anchor) {
myLeftStripe.revalidate();
}
else if (ToolWindowAnchor.BOTTOM == anchor) {
myBottomStripe.revalidate();
}
else if (ToolWindowAnchor.RIGHT == anchor) {
myRightStripe.revalidate();
}
else {
LOG.error("unknown anchor: " + anchor);
}
}
finally {
finish();
}
}
}
private final class MyUISettingsListenerImpl implements UISettingsListener {
public final void uiSettingsChanged(final UISettings source) {
updateToolStripesVisibility();
updateLayout();
}
}
private final class MyLayeredPane extends JBLayeredPane {
/*
* These images are used to perform animated showing and hiding of components.
* They are the member for performance reason.
*/
public MyLayeredPane(final JComponent splitter) {
setOpaque(false);
add(splitter, JLayeredPane.DEFAULT_LAYER);
}
/**
* When component size becomes larger then bottom and top images should be enlarged.
*/
public void doLayout() {
final int width = getWidth();
final int height = getHeight();
if (width < 0 || height < 0) {
return;
}
// Resize component at the DEFAULT layer. It should be only on component in that layer
Component[] components = getComponentsInLayer(JLayeredPane.DEFAULT_LAYER.intValue());
LOG.assertTrue(components.length <= 1);
for (final Component component : components) {
component.setBounds(0, 0, getWidth(), getHeight());
}
// Resize components at the PALETTE layer
components = getComponentsInLayer(JLayeredPane.PALETTE_LAYER.intValue());
for (final Component component : components) {
if (!(component instanceof InternalDecorator)) {
continue;
}
final WindowInfoImpl info = myDecorator2Info.get(component);
// In normal situation info is not null. But sometimes Swing sends resize
// event to removed component. See SCR #19566.
if (info == null) {
continue;
}
final float weight;
if (info.getAnchor().isHorizontal()) {
weight = (float)component.getHeight() / (float)getHeight();
}
else {
weight = (float)component.getWidth() / (float)getWidth();
}
setBoundsInPaletteLayer(component, info.getAnchor(), weight);
}
}
public final void setBoundsInPaletteLayer(final Component component, final ToolWindowAnchor anchor, float weight) {
if (weight < .0f) {
weight = WindowInfoImpl.DEFAULT_WEIGHT;
}
else if (weight > 1.0f) {
weight = 1.0f;
}
if (ToolWindowAnchor.TOP == anchor) {
component.setBounds(0, 0, getWidth(), (int)(getHeight() * weight + .5f));
}
else if (ToolWindowAnchor.LEFT == anchor) {
component.setBounds(0, 0, (int)(getWidth() * weight + .5f), getHeight());
}
else if (ToolWindowAnchor.BOTTOM == anchor) {
final int height = (int)(getHeight() * weight + .5f);
component.setBounds(0, getHeight() - height, getWidth(), height);
}
else if (ToolWindowAnchor.RIGHT == anchor) {
final int width = (int)(getWidth() * weight + .5f);
component.setBounds(getWidth() - width, 0, width, getHeight());
}
else {
LOG.error("unknown anchor " + anchor);
}
}
}
public void setStripesOverlayed(boolean stripesOverlayed) {
myStripesOverlayed = stripesOverlayed;
updateToolStripesVisibility();
}
@Override
public void dispose() {
}
private static float normalizeWeigh(final float weight) {
if (weight <= 0) return WindowInfoImpl.DEFAULT_WEIGHT;
if (weight >= 1) return 1 - WindowInfoImpl.DEFAULT_WEIGHT;
return weight;
}
}