| /* |
| * 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); |
| } |
| } |
| } |