blob: 8dc12fa4cec8acf065f0eed8d0497b427c7901fd [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.Disposable;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManagerListener;
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.Gray;
import com.intellij.ui.ScreenUtil;
import com.intellij.util.ui.UIUtil;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author Eugene Belyaev
*/
final class Stripe extends JPanel {
private final int myAnchor;
private final ArrayList<StripeButton> myButtons = new ArrayList<StripeButton>();
private final MyKeymapManagerListener myWeakKeymapManagerListener;
private final MyUISettingsListener myUISettingsListener;
private Dimension myPrefSize;
private StripeButton myDragButton;
private Rectangle myDropRectangle;
private final ToolWindowManagerImpl myManager;
private JComponent myDragButtonImage;
private LayoutData myLastLayoutData;
private boolean myFinishingDrop;
static final int DROP_DISTANCE_SENSIVITY = 20;
private final Disposable myDisposable = Disposer.newDisposable();
private BufferedImage myCachedBg;
Stripe(final int anchor, ToolWindowManagerImpl manager) {
super(new GridBagLayout());
setOpaque(true);
myManager = manager;
myAnchor = anchor;
myWeakKeymapManagerListener = new MyKeymapManagerListener();
myUISettingsListener = new MyUISettingsListener();
setBorder(new AdaptiveBorder());
}
private static class AdaptiveBorder implements Border {
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Insets insets = ((JComponent)c).getInsets();
if (UIUtil.isUnderDarcula()) {
g.setColor(Gray._40);
drawBorder(g, x, y, width, height, insets);
}
else {
g.setColor(UIUtil.getPanelBackground());
drawBorder(g, x, y, width, height, insets);
g.setColor(Gray._155);
drawBorder(g, x, y, width, height, insets);
}
}
private static void drawBorder(Graphics g, int x, int y, int width, int height, Insets insets) {
if (insets.top == 1) g.drawLine(x, y, x + width, y);
if (insets.right == 1) g.drawLine(x + width - 1, y, x + width - 1, y + height);
if (insets.left == 1) g.drawLine(x, y, x, y + height);
if (insets.bottom == 1) g.drawLine(x, y + height - 1, x + width, y + height - 1);
if (UIUtil.isUnderDarcula()) {
final Color c = g.getColor();
if (insets.top == 2) {
g.setColor(c);
g.drawLine(x, y, x + width, y);
g.setColor(Gray._85);
g.drawLine(x, y + 1, x + width, y + 1);
}
if (insets.right == 2) {
g.setColor(Gray._85);
g.drawLine(x + width - 1, y, x + width - 1, y + height);
g.setColor(c);
g.drawLine(x + width - 2, y, x + width - 2, y + height);
}
if (insets.left == 2) {
g.setColor(Gray._85);
g.drawLine(x + 1, y, x + 1, y + height);
g.setColor(c);
g.drawLine(x, y, x, y + height);
}
if (insets.bottom == 2) {
//do nothing
}
}
}
@Override
public Insets getBorderInsets(Component c) {
Stripe stripe = (Stripe)c;
ToolWindowAnchor anchor = stripe.getAnchor();
Insets result = new Insets(0, 0, 0, 0);
final int off = UIUtil.isUnderDarcula() ? 1 : 0;
if (anchor == ToolWindowAnchor.LEFT) {
result.top = 1;
result.right = 1 + off;
}
else if (anchor == ToolWindowAnchor.RIGHT) {
result.left = 1 + off;
result.top = 1;
}
else if (anchor == ToolWindowAnchor.TOP) {
result.bottom = 0;
//result.bottom = 1;
result.top = 1;
}
else {
result.top = 1 + off;
}
return result;
}
@Override
public boolean isBorderOpaque() {
return true;
}
}
/**
* Invoked when enclosed frame is being shown.
*/
@Override
public void addNotify() {
super.addNotify();
updatePresentation();
KeymapManagerEx.getInstanceEx().addWeakListener(myWeakKeymapManagerListener);
if (ScreenUtil.isStandardAddRemoveNotify(this)) {
UISettings.getInstance().addUISettingsListener(myUISettingsListener, myDisposable);
}
}
/**
* Invoked when enclosed frame is being disposed.
*/
@Override
public void removeNotify() {
KeymapManagerEx.getInstanceEx().removeWeakListener(myWeakKeymapManagerListener);
if (ScreenUtil.isStandardAddRemoveNotify(this)) {
Disposer.dispose(myDisposable);
}
super.removeNotify();
}
void addButton(final StripeButton button, final Comparator<StripeButton> comparator) {
myPrefSize = null;
myButtons.add(button);
Collections.sort(myButtons, comparator);
add(button);
revalidate();
}
void removeButton(final StripeButton button) {
myPrefSize = null;
myButtons.remove(button);
remove(button);
revalidate();
}
public List<StripeButton> getButtons() {
return Collections.unmodifiableList(myButtons);
}
@Override
public void invalidate() {
myPrefSize = null;
super.invalidate();
}
@Override
public void doLayout() {
if (!myFinishingDrop) {
myLastLayoutData = recomputeBounds(true, getSize());
}
}
private LayoutData recomputeBounds(boolean setBounds, Dimension toFitWith) {
return recomputeBounds(setBounds, toFitWith, false);
}
private LayoutData recomputeBounds(boolean setBounds, Dimension toFitWith, boolean noDrop) {
final LayoutData data = new LayoutData();
final int horizontaloffset = getHeight() - 2;
data.eachY = 0;
data.size = new Dimension();
data.gap = 0;
data.horizontal = isHorizontal();
data.dragInsertPosition = -1;
if (data.horizontal) {
data.eachX = horizontaloffset - 1;
data.eachY = 1;
}
else {
data.eachX = 0;
}
data.fitSize = toFitWith != null ? toFitWith : new Dimension();
final Rectangle stripeSensetiveRec =
new Rectangle(-DROP_DISTANCE_SENSIVITY, -DROP_DISTANCE_SENSIVITY, getWidth() + DROP_DISTANCE_SENSIVITY * 2,
getHeight() + DROP_DISTANCE_SENSIVITY * 2);
boolean processDrop = isDroppingButton() && stripeSensetiveRec.intersects(myDropRectangle) && !noDrop;
if (toFitWith == null) {
for (StripeButton eachButton : myButtons) {
if (!isConsideredInLayout(eachButton)) continue;
final Dimension eachSize = eachButton.getPreferredSize();
data.fitSize.width = Math.max(eachSize.width, data.fitSize.width);
data.fitSize.height = Math.max(eachSize.height, data.fitSize.height);
}
}
int gap = 0;
if (toFitWith != null) {
LayoutData layoutData = recomputeBounds(false, null, true);
if (data.horizontal) {
gap = toFitWith.width - horizontaloffset - layoutData.size.width - data.eachX;
}
else {
gap = toFitWith.height - layoutData.size.height - data.eachY;
}
if (processDrop) {
if (data.horizontal) {
gap -= myDropRectangle.width + data.gap;
}
else {
gap -= myDropRectangle.height + data.gap;
}
}
gap = Math.max(gap, 0);
}
int insertOrder = -1;
boolean sidesStarted = false;
for (StripeButton eachButton : getButtonsToLayOut()) {
insertOrder = eachButton.getDecorator().getWindowInfo().getOrder();
final Dimension eachSize = eachButton.getPreferredSize();
if (!sidesStarted && eachButton.getWindowInfo().isSplit()) {
if (processDrop) {
tryDroppingOnGap(data, gap, eachButton.getWindowInfo().getOrder());
}
if (data.horizontal) {
data.eachX += gap;
data.size.width += gap;
}
else {
data.eachY += gap;
data.size.height += gap;
}
sidesStarted = true;
}
if (processDrop && !data.dragTargetChoosen) {
if (data.horizontal) {
int distance = myDropRectangle.x - data.eachX;
if (distance < eachSize.width / 2 || (myDropRectangle.x + myDropRectangle.width) < eachSize.width / 2) {
layoutButton(data, myDragButtonImage, false);
data.dragInsertPosition = insertOrder;
data.dragToSide = sidesStarted;
data.dragTargetChoosen = true;
}
}
else {
int distance = myDropRectangle.y - data.eachY;
if (distance < eachSize.height / 2 || (myDropRectangle.y + myDropRectangle.height) < eachSize.height / 2) {
layoutButton(data, myDragButtonImage, false);
data.dragInsertPosition = insertOrder;
data.dragToSide = sidesStarted;
data.dragTargetChoosen = true;
}
}
}
layoutButton(data, eachButton, setBounds);
}
if (!sidesStarted && processDrop) {
tryDroppingOnGap(data, gap, -1);
}
if (isDroppingButton()) {
final Dimension dragSize = myDragButton.getPreferredSize();
if (getAnchor().isHorizontal() == myDragButton.getWindowInfo().getAnchor().isHorizontal()) {
data.size.width = Math.max(data.size.width, dragSize.width);
data.size.height = Math.max(data.size.height, dragSize.height);
}
else {
data.size.width = Math.max(data.size.width, dragSize.height);
data.size.height = Math.max(data.size.height, dragSize.width);
}
}
if (processDrop && !data.dragTargetChoosen) {
data.dragInsertPosition = -1;
data.dragToSide = true;
data.dragTargetChoosen = true;
}
return data;
}
private void tryDroppingOnGap(final LayoutData data, final int gap, final int insertOrder) {
if (data.dragTargetChoosen) return;
int nonSideDistance;
int sideDistance;
if (data.horizontal) {
nonSideDistance = myDropRectangle.x - data.eachX;
sideDistance = data.eachX + gap - myDropRectangle.x;
}
else {
nonSideDistance = myDropRectangle.y - data.eachY;
sideDistance = data.eachY + gap - myDropRectangle.y;
}
nonSideDistance = Math.max(0, nonSideDistance);
if (sideDistance > 0) {
if (nonSideDistance > sideDistance) {
data.dragInsertPosition = insertOrder;
data.dragToSide = true;
data.dragTargetChoosen = true;
}
else {
data.dragInsertPosition = -1;
data.dragToSide = false;
data.dragTargetChoosen = true;
}
layoutButton(data, myDragButtonImage, false);
}
}
private List<StripeButton> getButtonsToLayOut() {
List<StripeButton> result = new ArrayList<StripeButton>();
List<StripeButton> tools = new ArrayList<StripeButton>();
List<StripeButton> sideTools = new ArrayList<StripeButton>();
for (StripeButton b : myButtons) {
if (!isConsideredInLayout(b)) continue;
if (b.getWindowInfo().isSplit()) {
sideTools.add(b);
}
else {
tools.add(b);
}
}
result.addAll(tools);
result.addAll(sideTools);
return result;
}
public ToolWindowAnchor getAnchor() {
return ToolWindowAnchor.get(myAnchor);
}
private static void layoutButton(final LayoutData data, final JComponent eachButton, boolean setBounds) {
final Dimension eachSize = eachButton.getPreferredSize();
if (setBounds) {
final int width = data.horizontal ? eachSize.width : data.fitSize.width;
final int height = data.horizontal ? data.fitSize.height : eachSize.height;
eachButton.setBounds(data.eachX, data.eachY, width, height);
}
if (data.horizontal) {
final int deltaX = eachSize.width + data.gap;
data.eachX += deltaX;
data.size.width += deltaX;
data.size.height = eachSize.height;
}
else {
final int deltaY = eachSize.height + data.gap;
data.eachY += deltaY;
data.size.width = eachSize.width;
data.size.height += deltaY;
}
data.processedComponents++;
}
public void startDrag() {
revalidate();
repaint();
}
public void stopDrag() {
revalidate();
repaint();
}
public StripeButton getButtonFor(final String toolWindowId) {
for (StripeButton each : myButtons) {
if (each.getWindowInfo().getId().equals(toolWindowId)) return each;
}
return null;
}
public void setOverlayed(boolean overlayed) {
if (Registry.is("disable.toolwindow.overlay")) return;
Color bg = UIUtil.getPanelBackground();
if (UIUtil.isUnderAquaLookAndFeel()) {
float[] result = Color.RGBtoHSB(bg.getRed(), bg.getGreen(), bg.getBlue(), new float[3]);
bg = new Color(Color.HSBtoRGB(result[0], result[1], result[2] - 0.08f > 0 ? result[2] - 0.08f : result[2]));
}
if (overlayed) {
setBackground(ColorUtil.toAlpha(bg, 190));
}
else {
setBackground(bg);
}
}
private static class LayoutData {
int eachX;
int eachY;
int gap;
Dimension size;
Dimension fitSize;
boolean horizontal;
int processedComponents;
boolean dragTargetChoosen;
boolean dragToSide;
int dragInsertPosition;
}
private boolean isHorizontal() {
return myAnchor == SwingConstants.TOP || myAnchor == SwingConstants.BOTTOM;
}
@Override
public Dimension getPreferredSize() {
if (myPrefSize == null) {
myPrefSize = recomputeBounds(false, null).size;
}
return myPrefSize;
}
void updatePresentation() {
for (StripeButton button : myButtons) {
button.updatePresentation();
}
}
public boolean containsScreen(final Rectangle screenRec) {
final Point point = screenRec.getLocation();
SwingUtilities.convertPointFromScreen(point, this);
return new Rectangle(point, screenRec.getSize()).intersects(
new Rectangle(-DROP_DISTANCE_SENSIVITY,
-DROP_DISTANCE_SENSIVITY,
getWidth() + DROP_DISTANCE_SENSIVITY,
getHeight() + DROP_DISTANCE_SENSIVITY)
);
}
public void finishDrop() {
if (myLastLayoutData == null || !isDroppingButton()) return;
final WindowInfoImpl info = myDragButton.getDecorator().getWindowInfo();
myFinishingDrop = true;
myManager.setSideToolAndAnchor(info.getId(), ToolWindowAnchor.get(myAnchor), myLastLayoutData.dragInsertPosition,
myLastLayoutData.dragToSide);
myManager.invokeLater(new Runnable() {
@Override
public void run() {
resetDrop();
}
});
}
public void resetDrop() {
myDragButton = null;
myDragButtonImage = null;
myFinishingDrop = false;
myPrefSize = null;
revalidate();
repaint();
}
public void processDropButton(final StripeButton button, JComponent buttonImage, Point screenPoint) {
if (!isDroppingButton()) {
final BufferedImage image = UIUtil.createImage(button.getWidth(), button.getHeight(), BufferedImage.TYPE_INT_RGB);
buttonImage.paint(image.getGraphics());
myDragButton = button;
myDragButtonImage = buttonImage;
myPrefSize = null;
}
final Point point = new Point(screenPoint);
SwingUtilities.convertPointFromScreen(point, this);
myDropRectangle = new Rectangle(point, buttonImage.getSize());
revalidate();
repaint();
}
private boolean isDroppingButton() {
return myDragButton != null;
}
private boolean isConsideredInLayout(final StripeButton each) {
return each.isVisible();
}
private final class MyKeymapManagerListener implements KeymapManagerListener {
@Override
public void activeKeymapChanged(final Keymap keymap) {
updatePresentation();
}
}
private final class MyUISettingsListener implements UISettingsListener {
@Override
public void uiSettingsChanged(UISettings source) {
updatePresentation();
}
}
public String toString() {
String anchor = null;
switch (myAnchor) {
case SwingConstants.TOP:
anchor = "TOP";
break;
case SwingConstants.BOTTOM:
anchor = "BOTTOM";
break;
case SwingConstants.LEFT:
anchor = "LEFT";
break;
case SwingConstants.RIGHT:
anchor = "RIGHT";
break;
}
return getClass().getName() + " " + anchor;
}
private BufferedImage getCachedImage() {
if (myCachedBg == null) {
ToolWindowAnchor anchor = getAnchor();
Rectangle bounds = getBounds();
BufferedImage bg;
if (anchor == ToolWindowAnchor.LEFT || anchor == ToolWindowAnchor.RIGHT) {
bg = (BufferedImage)createImage(bounds.width, 50);
Graphics2D graphics = bg.createGraphics();
graphics.setPaint(UIUtil.getGradientPaint(0, 0, new Color(0, 0, 0, 10), bounds.width, 0, new Color(0, 0, 0, 0)));
graphics.fillRect(0, 0, bounds.width, 50);
graphics.dispose();
}
else {
bg = (BufferedImage)createImage(50, bounds.height);
Graphics2D graphics = bg.createGraphics();
graphics.setPaint(UIUtil.getGradientPaint(0, 0, new Color(0, 0, 0, 0), 0, bounds.height, new Color(0, 0, 0, 10)));
graphics.fillRect(0, 0, 50, bounds.height);
graphics.dispose();
}
myCachedBg = bg;
}
return myCachedBg;
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
if (!myFinishingDrop && isDroppingButton() && myDragButton.getParent() != this) {
g.setColor(getBackground().brighter());
g.fillRect(0, 0, getWidth(), getHeight());
}
if (UIUtil.isUnderDarcula()) return;
ToolWindowAnchor anchor = getAnchor();
g.setColor(new Color(255, 255, 255, 40));
Rectangle r = getBounds();
if (anchor == ToolWindowAnchor.LEFT || anchor == ToolWindowAnchor.RIGHT) {
g.drawLine(0, 0, 0, r.height);
g.drawLine(r.width - 2, 0, r.width - 2, r.height);
}
else {
g.drawLine(0, 1, r.width, 1);
g.drawLine(0, r.height - 1, r.width, r.height - 1);
}
}
}