blob: b4b88ee2f5a91f8df5489106a9060b16e10e3444 [file] [log] [blame]
/*
* Copyright 2000-2011 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.ui.components;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.ui.JBMenuItem;
import com.intellij.openapi.ui.JBPopupMenu;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.Weighted;
import com.intellij.openapi.wm.IdeGlassPane;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.ui.ScreenUtil;
import com.intellij.util.ui.UIUtil;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.HashSet;
import java.util.Set;
public class JBOptionButton extends JButton implements MouseMotionListener, Weighted {
private static final Insets myDownIconInsets = new Insets(0, 6, 0, 4);
private Rectangle myMoreRec;
private Rectangle myMoreRecMouse;
private Action[] myOptions;
private JPopupMenu myUnderPopup;
private JPopupMenu myAbovePopup;
private boolean myPopupIsShowing;
private String myOptionTooltipText;
private Set<OptionInfo> myOptionInfos = new HashSet<OptionInfo>();
private boolean myOkToProcessDefaultMnemonics = true;
private IdeGlassPane myGlassPane;
private final Disposable myDisposable = Disposer.newDisposable();
public JBOptionButton(Action action, Action[] options) {
super(action);
myOptions = options;
myMoreRec = new Rectangle(0, 0, AllIcons.General.ArrowDown.getIconWidth(), AllIcons.General.ArrowDown.getIconHeight());
myUnderPopup = fillMenu(true);
myAbovePopup = fillMenu(false);
enableEvents(MouseEvent.MOUSE_EVENT_MASK | MouseEvent.MOUSE_MOTION_EVENT_MASK);
}
@Override
public void addNotify() {
super.addNotify();
if (!ScreenUtil.isStandardAddRemoveNotify(this))
return;
myGlassPane = IdeGlassPaneUtil.find(this);
if (myGlassPane != null) {
myGlassPane.addMouseMotionPreprocessor(this, myDisposable);
}
}
@Override
public void removeNotify() {
super.removeNotify();
if (!ScreenUtil.isStandardAddRemoveNotify(this))
return;
Disposer.dispose(myDisposable);
}
@Override
public double getWeight() {
return 0.5;
}
@Override
public void mouseDragged(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
final MouseEvent event = SwingUtilities.convertMouseEvent(e.getComponent(), e, getParent());
final boolean insideRec = getBounds().contains(event.getPoint());
boolean buttonsNotPressed = (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == 0;
if (!myPopupIsShowing && insideRec && buttonsNotPressed) {
showPopup(null, false);
} else if (myPopupIsShowing && !insideRec) {
final Component over = SwingUtilities.getDeepestComponentAt(e.getComponent(), e.getX(), e.getY());
JPopupMenu popup = myUnderPopup.isShowing() ? myUnderPopup : myAbovePopup;
if (over != null && popup.isShowing()) {
final Rectangle rec = new Rectangle(popup.getLocationOnScreen(), popup.getSize());
int delta = 15;
rec.x -= delta;
rec.width += delta * 2;
rec.y -= delta;
rec.height += delta * 2;
final Point eventPoint = e.getPoint();
SwingUtilities.convertPointToScreen(eventPoint, e.getComponent());
if (rec.contains(eventPoint)) {
return;
}
}
closePopup();
}
}
@Override
public Dimension getPreferredSize() {
final Dimension size = super.getPreferredSize();
size.width += (myMoreRec.width + myDownIconInsets.left + myDownIconInsets.right);
size.height += (myDownIconInsets.top + myDownIconInsets.bottom);
return size;
}
@Override
public void doLayout() {
super.doLayout();
Insets insets = getInsets();
myMoreRec.x = getSize().width - myMoreRec.width - insets.right + 8;
myMoreRec.y = (getHeight() / 2 - myMoreRec.height / 2);
myMoreRecMouse = new Rectangle(myMoreRec.x - 8, 0, getWidth() - myMoreRec.x, getHeight());
}
@Override
public String getToolTipText(MouseEvent event) {
if (myMoreRec.x < event.getX()) {
return myOptionTooltipText;
} else {
return super.getToolTipText(event);
}
}
@Override
protected void processMouseEvent(MouseEvent e) {
if (myMoreRecMouse.contains(e.getPoint())) {
if (e.getID() == MouseEvent.MOUSE_PRESSED) {
if (!myPopupIsShowing) {
togglePopup();
}
}
}
else {
super.processMouseEvent(e);
}
}
public void togglePopup() {
if (myPopupIsShowing) {
closePopup();
} else {
showPopup(null, false);
}
}
public void showPopup(final Action actionToSelect, final boolean ensureSelection) {
if (myPopupIsShowing) return;
myPopupIsShowing = true;
final Point loc = getLocationOnScreen();
final Rectangle screen = ScreenUtil.getScreenRectangle(loc);
final Dimension popupSize = myUnderPopup.getPreferredSize();
final Rectangle intersection = screen.intersection(new Rectangle(new Point(loc.x, loc.y + getHeight()), popupSize));
final boolean above = intersection.height < popupSize.height;
int y = above ? getY() - popupSize.height : getY() + getHeight();
final JPopupMenu popup = above ? myAbovePopup : myUnderPopup;
final Ref<PopupMenuListener> listener = new Ref<PopupMenuListener>();
listener.set(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
if (popup != null && listener.get() != null) {
popup.removePopupMenuListener(listener.get());
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
myPopupIsShowing = false;
}
});
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
});
popup.addPopupMenuListener(listener.get());
popup.show(this, 0, y);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (popup == null || !popup.isShowing() || !myPopupIsShowing) return;
Action selection = actionToSelect;
if (selection == null && myOptions.length > 0 && ensureSelection) {
selection = getAction();
}
if (selection == null) return;
final MenuElement[] elements = popup.getSubElements();
for (MenuElement eachElement : elements) {
if (eachElement instanceof JMenuItem) {
JMenuItem eachItem = (JMenuItem)eachElement;
if (selection.equals(eachItem.getAction())) {
final MenuSelectionManager mgr = MenuSelectionManager.defaultManager();
final MenuElement[] path = new MenuElement[2];
path[0] = popup;
path[1] = eachItem;
mgr.setSelectedPath(path);
break;
}
}
}
}
});
}
public void closePopup() {
myUnderPopup.setVisible(false);
myAbovePopup.setVisible(false);
}
private JPopupMenu fillMenu(boolean under) {
final JPopupMenu result = new JBPopupMenu();
if (under && myOptions.length > 0) {
final JMenuItem mainAction = new JBMenuItem(getAction());
configureItem(getMenuInfo(getAction()), mainAction);
result.add(mainAction);
result.addSeparator();
}
for (Action each : myOptions) {
if (getAction() == each) continue;
final OptionInfo info = getMenuInfo(each);
final JMenuItem eachItem = new JBMenuItem(each);
configureItem(info, eachItem);
result.add(eachItem);
}
if (!under && myOptions.length > 0) {
result.addSeparator();
final JMenuItem mainAction = new JBMenuItem(getAction());
configureItem(getMenuInfo(getAction()), mainAction);
result.add(mainAction);
}
return result;
}
private void configureItem(OptionInfo info, JMenuItem eachItem) {
eachItem.setText(info.myPlainText);
if (info.myMnemonic >= 0) {
eachItem.setMnemonic(info.myMnemonic);
eachItem.setDisplayedMnemonicIndex(info.myMnemonicIndex);
}
myOptionInfos.add(info);
}
public boolean isOkToProcessDefaultMnemonics() {
return myOkToProcessDefaultMnemonics;
}
public static class OptionInfo {
String myPlainText;
int myMnemonic;
int myMnemonicIndex;
JBOptionButton myButton;
Action myAction;
OptionInfo(String plainText, int mnemonic, int mnemonicIndex, JBOptionButton button, Action action) {
myPlainText = plainText;
myMnemonic = mnemonic;
myMnemonicIndex = mnemonicIndex;
myButton = button;
myAction = action;
}
public String getPlainText() {
return myPlainText;
}
public int getMnemonic() {
return myMnemonic;
}
public int getMnemonicIndex() {
return myMnemonicIndex;
}
public JBOptionButton getButton() {
return myButton;
}
public Action getAction() {
return myAction;
}
}
private OptionInfo getMenuInfo(Action each) {
final String text = (String)each.getValue(Action.NAME);
int mnemonic = -1;
int mnemonicIndex = -1;
StringBuilder plainText = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '&' || ch == '_') {
if (i + 1 < text.length()) {
final char mnemonicsChar = text.charAt(i + 1);
mnemonic = Character.toUpperCase(mnemonicsChar);
mnemonicIndex = i;
}
continue;
}
plainText.append(ch);
}
return new OptionInfo(plainText.toString(), mnemonic, mnemonicIndex, this, each);
}
public Set<OptionInfo> getOptionInfos() {
return myOptionInfos;
}
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
boolean dark = UIUtil.isUnderDarcula();
int off = dark ? 6 : 0;
AllIcons.General.ArrowDown.paintIcon(this, g, myMoreRec.x - off, myMoreRec.y);
if (dark) return;
int y1 = myMoreRec.y - 2;
int y2 = myMoreRec.y + myMoreRec.height + 2;
final Insets insets = getInsets();
if (y1 < getInsets().top) {
y1 = insets.top;
}
if (y2 > getHeight() - insets.bottom) {
y2 = getHeight() - insets.bottom;
}
final int x = myMoreRec.x - 4;
UIUtil.drawDottedLine(((Graphics2D)g), x, y1, x, y2, null, Color.darkGray);
}
public void setOptionTooltipText(String text) {
myOptionTooltipText = text;
}
public void setOkToProcessDefaultMnemonics(boolean ok) {
myOkToProcessDefaultMnemonics = ok;
}
}