/* * Copyright 2003-2016 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 jetbrains.mps.nodeEditor.cells.contextAssistant; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.ListPopupStep; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.UserActivityProviderComponent; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MouseEventAdapter; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.DefaultButtonModel; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import java.awt.Component; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; /** * A combo-box button that shows menu based on a {@link ListPopupStep}. Copied from {@link com.intellij.openapi.actionSystem.ex.ComboBoxAction} and simplified. */ abstract class AbstractStepComboBoxButton extends JButton implements UserActivityProviderComponent { private boolean myForcePressed = false; private JBPopup myPopup; AbstractStepComboBoxButton(String text) { this(); setText(text + " \u25be"); // BLACK DOWN-POINTING SMALL TRIANGLE (U+25BE) } private AbstractStepComboBoxButton() { setModel(new MyButtonModel()); setHorizontalAlignment(LEFT); putClientProperty("styleCombo", Boolean.TRUE); Insets margins = getMargin(); setMargin(JBUI.insets(margins.top, 2, margins.bottom, 2)); setFont(UIUtil.getLabelFont()); setOpaque(true); InputMap inputMap = getInputMap(); inputMap.put(KeyStroke.getKeyStroke("DOWN"), "pressed"); inputMap.put(KeyStroke.getKeyStroke("released DOWN"), "released"); addActionListener( e -> { if (!myForcePressed) { IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(this::showPopup); } } ); addMouseListener(new MouseAdapter() { @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(MouseEventAdapter.convert(e, e.getComponent(), MouseEvent.MOUSE_MOVED, e.getWhen(), e.getModifiers() | e.getModifiersEx(), e.getX(), e.getY())); } @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); } } } @NotNull private Disposable setForcePressed() { myForcePressed = true; repaint(); return () -> { // give the button a chance to handle action listener ApplicationManager.getApplication().invokeLater(() -> { myForcePressed = false; myPopup = null; }, ModalityState.any()); repaint(); fireStateChanged(); }; } @Nullable @Override public String getToolTipText() { return myForcePressed ? null : super.getToolTipText(); } private void showPopup() { myPopup = JBPopupFactory.getInstance().createListPopup(getStep()); Disposer.register(myPopup, setForcePressed()); myPopup.showUnderneathOf(this); } protected abstract ListPopupStep<?> getStep(); @Override public Dimension getMinimumSize() { return new Dimension(super.getMinimumSize().width, getPreferredSize().height); } private class MyButtonModel extends DefaultButtonModel { @Override public boolean isPressed() { return myForcePressed || super.isPressed(); } @Override public boolean isArmed() { return myForcePressed || super.isArmed(); } } }