blob: 6ce522141f16dd67c2e6339ee4facd0b84c89ca7 [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.actionSystem.ex;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.*;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public abstract class ComboBoxAction extends AnAction implements CustomComponentAction {
private static final Icon ARROW_ICON = UIUtil.isUnderDarcula() ? AllIcons.General.ComboArrow : AllIcons.General.ComboBoxButtonArrow;
private static final Icon DISABLED_ARROW_ICON = IconLoader.getDisabledIcon(ARROW_ICON);
private boolean mySmallVariant = true;
private String myPopupTitle;
private DataContext myDataContext;
protected ComboBoxAction() {
}
@Override
public void actionPerformed(AnActionEvent e) {
}
@Override
public JComponent createCustomComponent(Presentation presentation) {
JPanel panel = new JPanel(new GridBagLayout());
ComboBoxButton button = createComboBoxButton(presentation);
panel.add(button,
new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 3, 0, 3), 0, 0));
return panel;
}
protected ComboBoxButton createComboBoxButton(Presentation presentation) {
return new ComboBoxButton(presentation);
}
public boolean isSmallVariant() {
return mySmallVariant;
}
public void setSmallVariant(boolean smallVariant) {
mySmallVariant = smallVariant;
}
public void setPopupTitle(String popupTitle) {
myPopupTitle = popupTitle;
}
@Override
public void update(AnActionEvent e) {
super.update(e);
myDataContext = e.getDataContext();
}
@NotNull
protected abstract DefaultActionGroup createPopupActionGroup(JComponent button);
protected int getMaxRows() {
return 30;
}
protected int getMinHeight() {
return 1;
}
protected int getMinWidth() {
return 1;
}
protected class ComboBoxButton extends JButton implements UserActivityProviderComponent {
private final Presentation myPresentation;
private boolean myForcePressed = false;
private PropertyChangeListener myButtonSynchronizer;
private boolean myMouseInside = false;
private JBPopup myPopup;
private boolean myForceTransparent = false;
public ComboBoxButton(Presentation presentation) {
myPresentation = presentation;
setEnabled(myPresentation.isEnabled());
setModel(new MyButtonModel());
setHorizontalAlignment(LEFT);
setFocusable(false);
Insets margins = getMargin();
setMargin(new Insets(margins.top, 2, margins.bottom, 2));
if (isSmallVariant()) {
setBorder(IdeBorderFactory.createEmptyBorder(0, 2, 0, 2));
if (!UIUtil.isUnderGTKLookAndFeel()) {
setFont(UIUtil.getLabelFont().deriveFont(11.0f));
}
}
addActionListener(
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!myForcePressed) {
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(new Runnable() {
@Override
public void run() {
showPopup();
}
});
}
}
}
);
//noinspection HardCodedStringLiteral
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
myMouseInside = true;
repaint();
}
@Override
public void mouseExited(MouseEvent e) {
myMouseInside = false;
repaint();
}
@Override
public void mousePressed(final MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
e.consume();
doClick();
}
}
@Override
public void mouseReleased(MouseEvent e) {
dispatchEventToPopup(e);
}
});
addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent e) {
mouseMoved(new MouseEvent(e.getComponent(),
MouseEvent.MOUSE_MOVED,
e.getWhen(),
e.getModifiers(),
e.getX(),
e.getY(),
e.getClickCount(),
e.isPopupTrigger(),
e.getButton()));
}
@Override
public void mouseMoved(MouseEvent e) {
dispatchEventToPopup(e);
}
});
}
// Event forwarding. We need it if user does press-and-drag gesture for opening popup and choosing item there.
// It works in JComboBox, here we provide the same behavior
private void dispatchEventToPopup(MouseEvent e) {
if (myPopup != null && myPopup.isVisible()) {
JComponent content = myPopup.getContent();
Rectangle rectangle = content.getBounds();
Point location = rectangle.getLocation();
SwingUtilities.convertPointToScreen(location, content);
Point eventPoint = e.getLocationOnScreen();
rectangle.setLocation(location);
if (rectangle.contains(eventPoint)) {
MouseEvent event = SwingUtilities.convertMouseEvent(e.getComponent(), e, myPopup.getContent());
Component component = SwingUtilities.getDeepestComponentAt(content, event.getX(), event.getY());
if (component != null)
component.dispatchEvent(event);
}
}
}
public void setForceTransparent(boolean transparent) {
myForceTransparent = transparent;
}
public void showPopup() {
myForcePressed = true;
repaint();
Runnable onDispose = new Runnable() {
@Override
public void run() {
// give button chance to handle action listener
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myForcePressed = false;
myPopup = null;
}
});
repaint();
fireStateChanged();
}
};
myPopup = createPopup(onDispose);
myPopup.show(new RelativePoint(this, new Point(0, getHeight() - 1)));
}
@Nullable
@Override
public String getToolTipText() {
return myForcePressed ? null : super.getToolTipText();
}
protected JBPopup createPopup(Runnable onDispose) {
DefaultActionGroup group = createPopupActionGroup(this);
DataContext context = getDataContext();
myDataContext = null;
final ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup(
myPopupTitle, group, context, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false, onDispose, getMaxRows());
popup.setMinimumSize(new Dimension(getMinWidth(), getMinHeight()));
return popup;
}
protected DataContext getDataContext() {
return myDataContext == null ? DataManager.getInstance().getDataContext(this) : myDataContext;
}
@Override
public void removeNotify() {
if (myButtonSynchronizer != null) {
myPresentation.removePropertyChangeListener(myButtonSynchronizer);
myButtonSynchronizer = null;
}
super.removeNotify();
}
@Override
public void addNotify() {
super.addNotify();
if (myButtonSynchronizer == null) {
myButtonSynchronizer = new MyButtonSynchronizer();
myPresentation.addPropertyChangeListener(myButtonSynchronizer);
}
initButton();
}
private void initButton() {
setIcon(myPresentation.getIcon());
setText(myPresentation.getText());
updateTooltipText(myPresentation.getDescription());
updateButtonSize();
}
private void updateTooltipText(String description) {
String tooltip = KeymapUtil.createTooltipText(description, ComboBoxAction.this);
setToolTipText(!tooltip.isEmpty() ? tooltip : null);
}
@Override
public void updateUI() {
super.updateUI();
//if (!UIUtil.isUnderGTKLookAndFeel()) {
// setBorder(UIUtil.getButtonBorder());
//}
//((JComponent)getParent().getParent()).revalidate();
}
protected class MyButtonModel extends DefaultButtonModel {
@Override
public boolean isPressed() {
return myForcePressed || super.isPressed();
}
@Override
public boolean isArmed() {
return myForcePressed || super.isArmed();
}
}
private class MyButtonSynchronizer implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (Presentation.PROP_TEXT.equals(propertyName)) {
setText((String)evt.getNewValue());
updateButtonSize();
}
else if (Presentation.PROP_DESCRIPTION.equals(propertyName)) {
updateTooltipText((String)evt.getNewValue());
}
else if (Presentation.PROP_ICON.equals(propertyName)) {
setIcon((Icon)evt.getNewValue());
updateButtonSize();
}
else if (Presentation.PROP_ENABLED.equals(propertyName)) {
setEnabled(((Boolean)evt.getNewValue()).booleanValue());
}
}
}
@Override
public Insets getInsets() {
final Insets insets = super.getInsets();
return new Insets(insets.top, insets.left, insets.bottom, insets.right + ARROW_ICON.getIconWidth());
}
@Override
public Insets getInsets(Insets insets) {
final Insets result = super.getInsets(insets);
if (UIUtil.isUnderNimbusLookAndFeel() && !isSmallVariant()) {
result.top += 2;
result.left += 8;
result.bottom += 2;
result.right += 4 + ARROW_ICON.getIconWidth();
}
else {
result.right += ARROW_ICON.getIconWidth();
}
return result;
}
@Override
public boolean isOpaque() {
return !isSmallVariant();
}
@Override
public Dimension getPreferredSize() {
final boolean isEmpty = getIcon() == null && StringUtil.isEmpty(getText());
int width = isEmpty ? 10 + ARROW_ICON.getIconWidth() : super.getPreferredSize().width;
if (isSmallVariant()) width += 4;
return new Dimension(width, isSmallVariant() ? 19 : UIUtil.isUnderNimbusLookAndFeel() ? 24 : 21);
}
@Override
public Font getFont() {
return SystemInfo.isMac ? UIUtil.getLabelFont(UIUtil.FontSize.SMALL) : UIUtil.getLabelFont();
}
@Override
public void paint(Graphics g) {
GraphicsUtil.setupAntialiasing(g);
GraphicsUtil.setupAAPainting(g);
final Dimension size = getSize();
final boolean isEmpty = getIcon() == null && StringUtil.isEmpty(getText());
final Color textColor = isEnabled()
? UIManager.getColor("Panel.foreground")
: UIUtil.getInactiveTextColor();
if (myForceTransparent) {
final Icon icon = getIcon();
int x = 7;
if (icon != null) {
icon.paintIcon(this, g, x, (size.height - icon.getIconHeight()) / 2);
x += icon.getIconWidth() + 3;
}
if (!StringUtil.isEmpty(getText())) {
final Font font = getFont();
g.setFont(font);
g.setColor(textColor);
g.drawString(getText(), x, (size.height + font.getSize()) / 2 - 1);
}
} else {
if (isSmallVariant()) {
final Graphics2D g2 = (Graphics2D)g;
g2.setColor(UIUtil.getControlColor());
final int w = getWidth();
final int h = getHeight();
if (getModel().isArmed() && getModel().isPressed()) {
g2.setPaint(UIUtil.getGradientPaint(0, 0, UIUtil.getControlColor(), 0, h, ColorUtil.shift(UIUtil.getControlColor(), 0.8)));
}
else {
if (UIUtil.isUnderDarcula()) {
g2.setPaint(UIUtil.getGradientPaint(0, 0, ColorUtil.shift(UIUtil.getControlColor(), 1.1), 0, h, ColorUtil.shift(UIUtil.getControlColor(), 0.9)));
} else {
g2.setPaint(UIUtil.getGradientPaint(0, 0, new JBColor(SystemInfo.isMac? Gray._226 : Gray._245, Gray._131), 0, h, new JBColor(SystemInfo.isMac? Gray._198 : Gray._208, Gray._128)));
}
}
g2.fillRoundRect(2, 0, w - 2, h, 5, 5);
Color borderColor = myMouseInside ? new JBColor(Gray._111, Gray._118) : new JBColor(Gray._151, Gray._95);
g2.setPaint(borderColor);
g2.drawRoundRect(2, 0, w - 3, h - 1, 5, 5);
final Icon icon = getIcon();
int x = 7;
if (icon != null) {
icon.paintIcon(this, g, x, (size.height - icon.getIconHeight()) / 2);
x += icon.getIconWidth() + 3;
}
if (!StringUtil.isEmpty(getText())) {
final Font font = getFont();
g2.setFont(font);
g2.setColor(textColor);
g2.drawString(getText(), x, (size.height + font.getSize()) / 2 - 1);
}
}
else {
paintComponent(g);
}
}
final Insets insets = super.getInsets();
final Icon icon = isEnabled() ? ARROW_ICON : DISABLED_ARROW_ICON;
final int x;
if (isEmpty) {
x = (size.width - icon.getIconWidth()) / 2;
}
else {
if (isSmallVariant()) {
x = size.width - icon.getIconWidth() - insets.right + 1;
}
else {
x = size.width - icon.getIconWidth() - insets.right + (UIUtil.isUnderNimbusLookAndFeel() ? -3 : 2);
}
}
icon.paintIcon(null, g, x, (size.height - icon.getIconHeight()) / 2);
g.setPaintMode();
}
protected void updateButtonSize() {
invalidate();
repaint();
setSize(getPreferredSize());
}
}
}