blob: f62ff2c1819a87050740e22ff99ecb431c4e6e67 [file] [log] [blame]
/*
* Copyright 2000-2014 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.featureStatistics.FeatureUsageTracker;
import com.intellij.ide.actions.ActivateToolWindowAction;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapManagerListener;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.ui.MouseDragHelper;
import com.intellij.ui.PopupHandler;
import com.intellij.util.ui.JBImageIcon;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
/**
* @author Eugene Belyaev
* @author Vladimir Kondratyev
*/
public final class StripeButton extends AnchoredButton implements ActionListener {
private final Color ourBackgroundColor = new Color(247, 243, 239);
/**
* This is analog of Swing mnemomic. We cannot use the standard ones
* because it causes typing of "funny" characters into the editor.
*/
private int myMnemonic;
private final InternalDecorator myDecorator;
private boolean myPressedWhenSelected;
private JLayeredPane myDragPane;
private final ToolWindowsPane myPane;
private JLabel myDragButtonImage;
private Point myPressedPoint;
private Stripe myLastStripe;
private KeyEventDispatcher myDragKeyEventDispatcher;
private boolean myDragCancelled = false;
private final StripeButton.MyKeymapListener myKeymapListener;
StripeButton(@NotNull final InternalDecorator decorator, ToolWindowsPane pane) {
myDecorator = decorator;
myKeymapListener = new MyKeymapListener();
myPane = pane;
init();
}
/**
* We are using the trick here: the method does all things that super method does
* excepting firing of the MNEMONIC_CHANGED_PROPERTY event. After that mnemonic
* doesn't work via standard Swing rules (processing of Alt keystrokes).
*/
@Override
public void setMnemonic(final int mnemonic) {
throw new UnsupportedOperationException("use setMnemonic2(int)");
}
private void setMnemonic2(final int mnemonic) {
myMnemonic = mnemonic;
revalidate();
repaint();
}
@Override
public int getMnemonic2() {
return myMnemonic;
}
@Override
public ToolWindowAnchor getAnchor() {
return getWindowInfo().getAnchor();
}
@NotNull
WindowInfoImpl getWindowInfo() {
return myDecorator.getWindowInfo();
}
private void init() {
setFocusable(false);
setBackground(ourBackgroundColor);
final Border border = BorderFactory.createEmptyBorder(5, 5, 0, 5);
setBorder(border);
updatePresentation();
apply(myDecorator.getWindowInfo());
addActionListener(this);
addMouseListener(new MyPopupHandler());
setRolloverEnabled(true);
setOpaque(false);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(final MouseEvent e) {
processDrag(e);
}
});
KeymapManager.getInstance().addKeymapManagerListener(myKeymapListener);
}
public boolean isFirst() {
return is(true);
}
public boolean isLast() {
return is(false);
}
public boolean isOppositeSide() {
return getWindowInfo().isSplit();
}
private boolean is(boolean first) {
Container parent = getParent();
if (parent == null) return false;
int max = first ? Integer.MAX_VALUE : 0;
ToolWindowAnchor anchor = getAnchor();
Component c = null;
int count = parent.getComponentCount();
for (int i = 0; i < count; i++) {
Component component = parent.getComponent(i);
if (!component.isVisible()) continue;
Rectangle r = component.getBounds();
if (anchor == ToolWindowAnchor.LEFT || anchor == ToolWindowAnchor.RIGHT) {
if (first && max > r.y || !first && max < r.y) {
max = r.y;
c = component;
}
} else {
if (first && max > r.x || !first && max < r.x) {
max = r.x;
c = component;
}
}
}
return c == this;
}
@NotNull
public InternalDecorator getDecorator() {
return myDecorator;
}
private void processDrag(final MouseEvent e) {
if (myDragCancelled || !MouseDragHelper.checkModifiers(e)) return;
if (!isDraggingNow()) {
if (myPressedPoint == null) return;
if (isWithinDeadZone(e)) return;
myDragPane = findLayeredPane(e);
if (myDragPane == null) return;
final BufferedImage image = UIUtil.createImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
paint(image.getGraphics());
myDragButtonImage = new JLabel(new JBImageIcon(image)) {
public String toString() {
return "Image for: " + StripeButton.this.toString();
}
};
myDragButtonImage.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
finishDragging();
myPressedPoint = null;
myDragButtonImage = null;
super.mouseReleased(e);
}
});
myDragPane.add(myDragButtonImage, JLayeredPane.POPUP_LAYER);
myDragButtonImage.setSize(myDragButtonImage.getPreferredSize());
setVisible(false);
myPane.startDrag();
myDragKeyEventDispatcher = new DragKeyEventDispatcher();
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(myDragKeyEventDispatcher);
}
if (!isDraggingNow()) return;
Point xy = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), myDragPane);
if (myPressedPoint != null) {
xy.x -= myPressedPoint.x;
xy.y -= myPressedPoint.y;
}
myDragButtonImage.setLocation(xy);
SwingUtilities.convertPointToScreen(xy, myDragPane);
final Stripe stripe = myPane.getStripeFor(new Rectangle(xy, myDragButtonImage.getSize()), (Stripe)getParent());
if (stripe == null) {
if (myLastStripe != null) {
myLastStripe.resetDrop();
}
} else {
if (myLastStripe != null && myLastStripe != stripe) {
myLastStripe.resetDrop();
}
stripe.processDropButton(this, myDragButtonImage, xy);
}
myLastStripe = stripe;
}
private class DragKeyEventDispatcher implements KeyEventDispatcher {
@Override
public boolean dispatchKeyEvent(KeyEvent e) {
if (isDraggingNow() && e.getKeyCode() == KeyEvent.VK_ESCAPE && e.getID() == KeyEvent.KEY_PRESSED) {
myDragCancelled = true;
finishDragging();
return true;
}
return false;
}
}
private boolean isWithinDeadZone(final MouseEvent e) {
return Math.abs(myPressedPoint.x - e.getPoint().x) < MouseDragHelper.DRAG_START_DEADZONE && Math.abs(myPressedPoint.y - e.getPoint().y) < MouseDragHelper
.DRAG_START_DEADZONE;
}
@Nullable
private static JLayeredPane findLayeredPane(MouseEvent e) {
if (!(e.getComponent() instanceof JComponent)) return null;
final JRootPane root = ((JComponent)e.getComponent()).getRootPane();
return root.getLayeredPane();
}
@Override
protected void processMouseEvent(final MouseEvent e) {
if (e.isPopupTrigger() && e.getComponent().isShowing()) {
super.processMouseEvent(e);
return;
}
if (UIUtil.isCloseClick(e)) {
myDecorator.fireHiddenSide();
return;
}
if (e.getButton() == MouseEvent.BUTTON1) {
if (MouseEvent.MOUSE_PRESSED == e.getID()) {
myPressedPoint = e.getPoint();
myPressedWhenSelected = isSelected();
myDragCancelled = false;
}
else if (MouseEvent.MOUSE_RELEASED == e.getID()) {
finishDragging();
myPressedPoint = null;
myDragButtonImage = null;
}
}
super.processMouseEvent(e);
}
@Override
public void actionPerformed(final ActionEvent e) {
if (myPressedWhenSelected) {
myDecorator.fireHidden();
}
else {
myDecorator.fireActivated();
}
myPressedWhenSelected = false;
FeatureUsageTracker.getInstance().triggerFeatureUsed("toolwindow.clickstat." + myDecorator.getToolWindow().getId());
}
public void apply(@NotNull WindowInfoImpl info) {
setSelected(info.isVisible() || info.isActive());
}
void dispose() {
KeymapManager.getInstance().removeKeymapManagerListener(myKeymapListener);
}
private void showPopup(final Component component, final int x, final int y) {
final ActionGroup group = myDecorator.createPopupGroup();
final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group);
popupMenu.getComponent().show(component, x, y);
}
@Override
public void updateUI() {
setUI(StripeButtonUI.createUI(this));
Font font = UIUtil.getLabelFont(UIUtil.FontSize.SMALL);
/*
if (font.getSize() % 2 == 1) { // that's a trick. Size of antialiased font isn't properly calculated for fonts with odd size
font = font.deriveFont(font.getStyle(), font.getSize() - 1);
}
*/
setFont(font);
}
void updatePresentation() {
updateState();
updateText();
Icon icon = myDecorator.getToolWindow().getIcon();
setIcon(icon);
setDisabledIcon(IconLoader.getDisabledIcon(icon));
}
private void updateText() {
String text = myDecorator.getToolWindow().getStripeTitle();
if (UISettings.getInstance().SHOW_TOOL_WINDOW_NUMBERS) {
String toolWindowId = myDecorator.getToolWindow().getId();
int mnemonic = ActivateToolWindowAction.getMnemonicForToolWindow(toolWindowId);
if (mnemonic != -1) {
text = (char)mnemonic + ": " + text;
setMnemonic2(mnemonic);
}
else {
setMnemonic2(0);
}
}
setText(text);
}
private void updateState() {
ToolWindowImpl window = myDecorator.getToolWindow();
boolean toShow = window.isAvailable() || window.isPlaceholderMode();
if (UISettings.getInstance().ALWAYS_SHOW_WINDOW_BUTTONS) {
setVisible(true);
}
else {
setVisible(toShow);
}
setEnabled(toShow && !window.isPlaceholderMode());
}
private final class MyPopupHandler extends PopupHandler {
@Override
public void invokePopup(final Component component, final int x, final int y) {
showPopup(component, x, y);
}
}
private final class MyKeymapListener implements KeymapManagerListener {
@Override
public void activeKeymapChanged(Keymap keymap) {
updatePresentation();
}
}
private boolean isDraggingNow() {
return myDragButtonImage != null;
}
private void finishDragging() {
if (!isDraggingNow()) return;
myDragPane.remove(myDragButtonImage);
myDragButtonImage = null;
myPane.stopDrag();
myDragPane.repaint();
setVisible(true);
if (myLastStripe != null) {
myLastStripe.finishDrop();
myLastStripe = null;
}
if (myDragKeyEventDispatcher != null) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(myDragKeyEventDispatcher);
myDragKeyEventDispatcher = null;
}
}
public String toString() {
return StringUtil.getShortName(getClass().getName()) + " text: " + getText();
}
}