| /* |
| * Copyright 2000-2013 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.ui.popup; |
| |
| import com.intellij.openapi.actionSystem.DataProvider; |
| import com.intellij.openapi.actionSystem.PlatformDataKeys; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.ui.*; |
| import com.intellij.ui.awt.RelativePoint; |
| import com.intellij.ui.components.JBScrollPane; |
| import com.intellij.ui.components.JBViewport; |
| import com.intellij.ui.speedSearch.ListWithFilter; |
| import com.intellij.ui.treeStructure.treetable.TreeTable; |
| import com.intellij.util.BooleanFunction; |
| import com.intellij.util.Function; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import org.jetbrains.annotations.Nls; |
| import org.jetbrains.annotations.NonNls; |
| 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.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * @author max |
| */ |
| public class PopupChooserBuilder { |
| |
| private JComponent myChooserComponent; |
| private String myTitle; |
| private final ArrayList<KeyStroke> myAdditionalKeystrokes = new ArrayList<KeyStroke>(); |
| private Runnable myItemChosenRunnable; |
| private JComponent mySouthComponent; |
| private JComponent myEastComponent; |
| |
| private JBPopup myPopup; |
| |
| private boolean myRequestFocus = true; |
| private boolean myForceResizable = false; |
| private boolean myForceMovable = false; |
| private String myDimensionServiceKey = null; |
| private Computable<Boolean> myCancelCallback; |
| private boolean myAutoselect = true; |
| private float myAlpha; |
| private Component[] myFocusOwners = new Component[0]; |
| private boolean myCancelKeyEnabled = true; |
| |
| private final List<JBPopupListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private String myAd; |
| private Dimension myMinSize; |
| private ActiveComponent myCommandButton; |
| private final List<Pair<ActionListener,KeyStroke>> myKeyboardActions = new ArrayList<Pair<ActionListener, KeyStroke>>(); |
| private Component mySettingsButtons; |
| private boolean myAutoselectOnMouseMove = true; |
| |
| private Function<Object,String> myItemsNamer = null; |
| private boolean myMayBeParent; |
| private int myAdAlignment = SwingUtilities.LEFT; |
| private boolean myModalContext; |
| private boolean myCloseOnEnter = true; |
| private boolean myCancelOnWindowDeactivation = true; |
| private boolean myUseForXYLocation; |
| @Nullable private Processor<JBPopup> myCouldPin; |
| |
| public PopupChooserBuilder setCancelOnClickOutside(boolean cancelOnClickOutside) { |
| myCancelOnClickOutside = cancelOnClickOutside; |
| return this; |
| } |
| |
| private boolean myCancelOnClickOutside = true; |
| |
| |
| |
| public JScrollPane getScrollPane() { |
| return myScrollPane; |
| } |
| |
| private JScrollPane myScrollPane; |
| |
| public PopupChooserBuilder(@NotNull JList list) { |
| myChooserComponent = list; |
| } |
| |
| public PopupChooserBuilder(@NotNull JTable table) { |
| myChooserComponent = table; |
| } |
| |
| public PopupChooserBuilder(@NotNull JTree tree) { |
| myChooserComponent = tree; |
| } |
| |
| @NotNull |
| public PopupChooserBuilder setTitle(@NotNull @Nls String title) { |
| myTitle = title; |
| return this; |
| } |
| |
| @NotNull |
| public PopupChooserBuilder addAdditionalChooseKeystroke(@Nullable KeyStroke keyStroke) { |
| if (keyStroke != null) { |
| myAdditionalKeystrokes.add(keyStroke); |
| } |
| return this; |
| } |
| |
| @NotNull |
| public PopupChooserBuilder setItemChoosenCallback(@NotNull Runnable runnable) { |
| myItemChosenRunnable = runnable; |
| return this; |
| } |
| |
| @NotNull |
| public PopupChooserBuilder setSouthComponent(@NotNull JComponent cmp) { |
| mySouthComponent = cmp; |
| return this; |
| } |
| |
| @NotNull |
| public PopupChooserBuilder setCouldPin(@Nullable Processor<JBPopup> callback){ |
| myCouldPin = callback; |
| return this; |
| } |
| |
| @NotNull |
| public PopupChooserBuilder setEastComponent(@NotNull JComponent cmp) { |
| myEastComponent = cmp; |
| return this; |
| } |
| |
| |
| public PopupChooserBuilder setRequestFocus(final boolean requestFocus) { |
| myRequestFocus = requestFocus; |
| return this; |
| } |
| |
| public PopupChooserBuilder setResizable(final boolean forceResizable) { |
| myForceResizable = forceResizable; |
| return this; |
| } |
| |
| |
| public PopupChooserBuilder setMovable(final boolean forceMovable) { |
| myForceMovable = forceMovable; |
| return this; |
| } |
| |
| public PopupChooserBuilder setDimensionServiceKey(@NonNls String key){ |
| myDimensionServiceKey = key; |
| return this; |
| } |
| |
| public PopupChooserBuilder setUseDimensionServiceForXYLocation(boolean use) { |
| myUseForXYLocation = use; |
| return this; |
| } |
| |
| public PopupChooserBuilder setCancelCallback(Computable<Boolean> callback) { |
| myCancelCallback = callback; |
| return this; |
| } |
| |
| public PopupChooserBuilder setCommandButton(@NotNull ActiveComponent commandButton) { |
| myCommandButton = commandButton; |
| return this; |
| } |
| |
| public PopupChooserBuilder setAlpha(final float alpha) { |
| myAlpha = alpha; |
| return this; |
| } |
| |
| public PopupChooserBuilder setAutoselectOnMouseMove(final boolean doAutoSelect) { |
| myAutoselectOnMouseMove = doAutoSelect; |
| return this; |
| } |
| |
| public PopupChooserBuilder setFilteringEnabled(Function<Object, String> namer) { |
| myItemsNamer = namer; |
| return this; |
| } |
| |
| public PopupChooserBuilder setModalContext(boolean modalContext) { |
| myModalContext = modalContext; |
| return this; |
| } |
| |
| @NotNull |
| public JBPopup createPopup() { |
| final JList list; |
| BooleanFunction<KeyEvent> keyEventHandler = null; |
| if (myChooserComponent instanceof JList) { |
| list = (JList)myChooserComponent; |
| myChooserComponent = ListWithFilter.wrap(list, new MyListWrapper(list), myItemsNamer); |
| keyEventHandler = new BooleanFunction<KeyEvent>() { |
| @Override |
| public boolean fun(KeyEvent keyEvent) { |
| return keyEvent.isConsumed(); |
| } |
| }; |
| } |
| else { |
| list = null; |
| } |
| |
| JPanel contentPane = new JPanel(new BorderLayout()); |
| if (!myForceMovable && myTitle != null) { |
| JLabel label = new JLabel(myTitle); |
| label.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); |
| label.setHorizontalAlignment(SwingConstants.CENTER); |
| contentPane.add(label, BorderLayout.NORTH); |
| } |
| |
| if (list != null) { |
| if (list.getSelectedIndex() == -1 && myAutoselect) { |
| list.setSelectedIndex(0); |
| } |
| } |
| |
| |
| (list != null ? list : myChooserComponent).addMouseListener(new MouseAdapter() { |
| @Override |
| public void mouseReleased(MouseEvent e) { |
| if (UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !UIUtil.isSelectionButtonDown(e) && !e.isConsumed()) { |
| if (myCloseOnEnter) { |
| closePopup(true, e, true); |
| } |
| else { |
| myItemChosenRunnable.run(); |
| } |
| } |
| } |
| }); |
| |
| registerClosePopupKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), false); |
| if (myCloseOnEnter) { |
| registerClosePopupKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), true); |
| } |
| else { |
| registerKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent event) { |
| myItemChosenRunnable.run(); |
| } |
| }); |
| } |
| for (KeyStroke keystroke : myAdditionalKeystrokes) { |
| registerClosePopupKeyboardAction(keystroke, true); |
| } |
| |
| if (myChooserComponent instanceof ListWithFilter) { |
| myScrollPane = ((ListWithFilter)myChooserComponent).getScrollPane(); |
| } |
| else if (myChooserComponent instanceof JTable) { |
| myScrollPane = createScrollPane((JTable)myChooserComponent); |
| } |
| else if (myChooserComponent instanceof JTree) { |
| myScrollPane = createScrollPane((JTree)myChooserComponent); |
| } |
| else { |
| throw new IllegalStateException("PopupChooserBuilder is intended to be constructed with one of JTable, JTree, JList components"); |
| } |
| |
| myScrollPane.getViewport().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| Insets viewportPadding = UIUtil.getListViewportPadding(); |
| ((JComponent)myScrollPane.getViewport().getView()).setBorder(BorderFactory.createEmptyBorder(viewportPadding.top, viewportPadding.left, viewportPadding.bottom, viewportPadding.right)); |
| |
| if (myChooserComponent instanceof ListWithFilter) { |
| addCenterComponentToContentPane(contentPane, myChooserComponent); |
| } |
| else { |
| addCenterComponentToContentPane(contentPane, myScrollPane); |
| } |
| |
| if (mySouthComponent != null) { |
| addSouthComponentToContentPane(contentPane, mySouthComponent); |
| } |
| |
| if (myEastComponent != null) { |
| addEastComponentToContentPane(contentPane, myEastComponent); |
| } |
| |
| ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(contentPane, myChooserComponent); |
| for (JBPopupListener each : myListeners) { |
| builder.addListener(each); |
| } |
| |
| builder.setDimensionServiceKey(null, myDimensionServiceKey, myUseForXYLocation) |
| .setRequestFocus(myRequestFocus) |
| .setResizable(myForceResizable) |
| .setMovable(myForceMovable) |
| .setTitle(myForceMovable ? myTitle : null) |
| .setCancelCallback(myCancelCallback) |
| .setAlpha(myAlpha) |
| .setFocusOwners(myFocusOwners) |
| .setCancelKeyEnabled(myCancelKeyEnabled) |
| .setAdText(myAd, myAdAlignment) |
| .setKeyboardActions(myKeyboardActions) |
| .setMayBeParent(myMayBeParent) |
| .setLocateWithinScreenBounds(true) |
| .setCancelOnOtherWindowOpen(true) |
| .setModalContext(myModalContext) |
| .setCancelOnWindowDeactivation(myCancelOnWindowDeactivation) |
| .setCancelOnClickOutside(myCancelOnClickOutside) |
| .setCouldPin(myCouldPin); |
| |
| if (keyEventHandler != null) { |
| builder.setKeyEventHandler(keyEventHandler); |
| } |
| |
| if (myCommandButton != null) { |
| builder.setCommandButton(myCommandButton); |
| } |
| |
| if (myMinSize != null) { |
| builder.setMinSize(myMinSize); |
| } |
| if (mySettingsButtons != null) { |
| builder.setSettingButtons(mySettingsButtons); |
| } |
| myPopup = builder.createPopup(); |
| return myPopup; |
| } |
| |
| protected void addEastComponentToContentPane(JPanel contentPane, JComponent component) { |
| contentPane.add(component, BorderLayout.EAST); |
| } |
| |
| protected void addSouthComponentToContentPane(JPanel contentPane, JComponent component) { |
| contentPane.add(component, BorderLayout.SOUTH); |
| } |
| |
| protected void addCenterComponentToContentPane(JPanel contentPane, JComponent component) { |
| contentPane.add(component, BorderLayout.CENTER); |
| } |
| |
| |
| public PopupChooserBuilder setMinSize(final Dimension dimension) { |
| myMinSize = dimension; |
| return this; |
| } |
| |
| public PopupChooserBuilder registerKeyboardAction(KeyStroke keyStroke, ActionListener actionListener) { |
| myKeyboardActions.add(Pair.create(actionListener, keyStroke)); |
| return this; |
| } |
| |
| private void registerClosePopupKeyboardAction(final KeyStroke keyStroke, final boolean shouldPerformAction) { |
| registerPopupKeyboardAction(keyStroke, new AbstractAction() { |
| public void actionPerformed(ActionEvent e) { |
| if (!shouldPerformAction && myChooserComponent instanceof ListWithFilter) { |
| if (((ListWithFilter)myChooserComponent).resetFilter()) return; |
| } |
| closePopup(shouldPerformAction, null, shouldPerformAction); |
| } |
| }); |
| } |
| |
| private void registerPopupKeyboardAction(final KeyStroke keyStroke, AbstractAction action) { |
| myChooserComponent.registerKeyboardAction(action, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); |
| } |
| |
| private void closePopup(boolean shouldPerformAction, MouseEvent e, boolean isOk) { |
| if (shouldPerformAction) { |
| myPopup.setFinalRunnable(myItemChosenRunnable); |
| } |
| |
| if (isOk) { |
| myPopup.closeOk(e); |
| } else { |
| myPopup.cancel(e); |
| } |
| } |
| |
| @NotNull |
| private JScrollPane createScrollPane(final JTable table) { |
| if (table instanceof TreeTable) { |
| TreeUtil.expandAll(((TreeTable)table).getTree()); |
| } |
| |
| JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(table); |
| |
| scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); |
| |
| if (table.getSelectedRow() == -1) { |
| table.getSelectionModel().setSelectionInterval(0, 0); |
| } |
| |
| if (table.getRowCount() >= 20) { |
| scrollPane.getViewport().setPreferredSize(new Dimension(table.getPreferredScrollableViewportSize().width, 300)); |
| } |
| else { |
| scrollPane.getViewport().setPreferredSize(table.getPreferredSize()); |
| } |
| |
| if (myAutoselectOnMouseMove) { |
| table.addMouseMotionListener(new MouseMotionAdapter() { |
| boolean myIsEngaged = false; |
| public void mouseMoved(MouseEvent e) { |
| if (myIsEngaged) { |
| int index = table.rowAtPoint(e.getPoint()); |
| table.getSelectionModel().setSelectionInterval(index, index); |
| } |
| else { |
| myIsEngaged = true; |
| } |
| } |
| }); |
| } |
| |
| return scrollPane; |
| } |
| |
| @NotNull |
| private JScrollPane createScrollPane(final JTree tree) { |
| TreeUtil.expandAll(tree); |
| |
| JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(tree); |
| |
| scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); |
| |
| if (tree.getSelectionCount() == 0) { |
| tree.setSelectionRow(0); |
| } |
| |
| if (tree.getRowCount() >= 20) { |
| scrollPane.getViewport().setPreferredSize(new Dimension(tree.getPreferredScrollableViewportSize().width, 300)); |
| } |
| else { |
| scrollPane.getViewport().setPreferredSize(tree.getPreferredSize()); |
| } |
| |
| if (myAutoselectOnMouseMove) { |
| tree.addMouseMotionListener(new MouseMotionAdapter() { |
| boolean myIsEngaged = false; |
| public void mouseMoved(MouseEvent e) { |
| if (myIsEngaged) { |
| final Point p = e.getPoint(); |
| int index = tree.getRowForLocation(p.x, p.y); |
| tree.setSelectionRow(index); |
| } |
| else { |
| myIsEngaged = true; |
| } |
| } |
| }); |
| } |
| |
| return scrollPane; |
| } |
| |
| public PopupChooserBuilder setAutoSelectIfEmpty(final boolean autoselect) { |
| myAutoselect = autoselect; |
| return this; |
| } |
| |
| public PopupChooserBuilder setCancelKeyEnabled(final boolean enabled) { |
| myCancelKeyEnabled = enabled; |
| return this; |
| } |
| |
| public PopupChooserBuilder addListener(final JBPopupListener listener) { |
| myListeners.add(listener); |
| return this; |
| } |
| |
| public PopupChooserBuilder setSettingButton(Component abutton) { |
| mySettingsButtons = abutton; |
| return this; |
| } |
| |
| public PopupChooserBuilder setMayBeParent(boolean mayBeParent) { |
| myMayBeParent = mayBeParent; |
| return this; |
| } |
| |
| public PopupChooserBuilder setCloseOnEnter(boolean closeOnEnter) { |
| myCloseOnEnter = closeOnEnter; |
| return this; |
| } |
| |
| private class MyListWrapper extends JBScrollPane implements DataProvider { |
| @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) |
| private final JList myList; |
| |
| private MyListWrapper(final JList list) { |
| super(UIUtil.isUnderAquaLookAndFeel() ? 0 : -1); |
| JBViewport viewport = new JBViewport() { |
| @Override |
| protected LayoutManager createLayoutManager() { |
| return new ViewportLayout() { |
| @Override |
| public Dimension preferredLayoutSize(Container parent) { |
| int size = list.getModel().getSize(); |
| if (size >= 0 && size <= 20) { |
| return list.getPreferredSize(); |
| } else { |
| final Dimension sz = super.preferredLayoutSize(parent); |
| final Point p = RelativePoint.getNorthWestOf(myList).getScreenPoint(); |
| final Rectangle screen = ScreenUtil.getScreenRectangle(p); |
| |
| final int bordersEtc = 20; |
| final int maxWidth = Math.abs(screen.x + screen.width - p.x) - 2 * bordersEtc; |
| return new Dimension(Math.min(maxWidth, sz.width) + bordersEtc, sz.height + list.getCellBounds(0, 0).height / 2); |
| } |
| } |
| }; |
| } |
| }; |
| list.setVisibleRowCount(15); |
| setViewport(viewport); |
| setViewportView(list); |
| |
| |
| if (myAutoselectOnMouseMove) { |
| ListUtil.installAutoSelectOnMouseMove(list); |
| } |
| |
| ListScrollingUtil.installActions(list); |
| |
| setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); |
| myList = list; |
| } |
| |
| @Nullable |
| public Object getData(@NonNls String dataId) { |
| if (PlatformDataKeys.SELECTED_ITEM.is(dataId)){ |
| return myList.getSelectedValue(); |
| } |
| if (PlatformDataKeys.SELECTED_ITEMS.is(dataId)){ |
| return myList.getSelectedValues(); |
| } |
| return null; |
| } |
| |
| public void setBorder(Border border) { |
| if (myList != null){ |
| myList.setBorder(border); |
| } |
| } |
| |
| public void requestFocus() { |
| myList.requestFocus(); |
| } |
| |
| public synchronized void addMouseListener(MouseListener l) { |
| myList.addMouseListener(l); |
| } |
| } |
| |
| @NotNull |
| public PopupChooserBuilder setFocusOwners(@NotNull Component[] focusOwners) { |
| myFocusOwners = focusOwners; |
| return this; |
| } |
| |
| @NotNull |
| public PopupChooserBuilder setAdText(String ad) { |
| setAdText(ad, SwingUtilities.LEFT); |
| return this; |
| } |
| |
| public PopupChooserBuilder setAdText(String ad, int alignment) { |
| myAd = ad; |
| myAdAlignment = alignment; |
| return this; |
| } |
| |
| public PopupChooserBuilder setCancelOnWindowDeactivation(boolean cancelOnWindowDeactivation) { |
| myCancelOnWindowDeactivation = cancelOnWindowDeactivation; |
| return this; |
| } |
| } |