Better multiselection for JList based popups
[fedora-idea.git] / platform-api / src / com / intellij / openapi / ui / popup / PopupChooserBuilder.java
blob60bea0cc4110dc78b461fffdba162559b8ab9e57
1 /*
2 * Copyright 2000-2007 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.openapi.ui.popup;
19 import com.intellij.openapi.actionSystem.DataConstants;
20 import com.intellij.openapi.actionSystem.DataProvider;
21 import com.intellij.openapi.util.Computable;
22 import com.intellij.ui.ListScrollingUtil;
23 import com.intellij.util.ui.tree.TreeUtil;
24 import com.intellij.util.ui.treetable.TreeTable;
25 import org.jetbrains.annotations.Nls;
26 import org.jetbrains.annotations.NonNls;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
30 import javax.swing.*;
31 import javax.swing.border.Border;
32 import java.awt.*;
33 import java.awt.event.*;
34 import java.util.ArrayList;
36 /**
37 * @author max
39 public class PopupChooserBuilder {
40 private JComponent myChooserComponent;
41 private String myTitle;
42 private ArrayList<KeyStroke> myAdditionalKeystrokes = new ArrayList<KeyStroke>();
43 private Runnable myItemChoosenRunnable;
44 private JComponent mySouthComponent;
46 private JBPopup myPopup;
48 private boolean myRequestFocus = true;
49 private boolean myForceResizable = false;
50 private boolean myForceMovable = false;
51 private String myDimensionServiceKey = null;
52 private Computable<Boolean> myCancelCallback;
53 private boolean myAutoselect = true;
54 private float myAlpha;
55 private Component[] myFocusOwners = new Component[0];
56 private boolean myCancelKeyEnabled = true;
58 ArrayList<JBPopupListener> myListeners = new ArrayList<JBPopupListener>();
59 private String myAd;
60 private Dimension myMinSize;
63 public PopupChooserBuilder(@NotNull JList list) {
64 myChooserComponent = new MyListWrapper(list);
67 public PopupChooserBuilder(@NotNull JTable table) {
68 myChooserComponent = table;
71 public PopupChooserBuilder(@NotNull JTree tree) {
72 myChooserComponent = tree;
75 @NotNull
76 public PopupChooserBuilder setTitle(@NotNull @Nls String title) {
77 myTitle = title;
78 return this;
81 @NotNull
82 public PopupChooserBuilder addAdditionalChooseKeystroke(@Nullable KeyStroke keyStroke) {
83 if (keyStroke != null) {
84 myAdditionalKeystrokes.add(keyStroke);
86 return this;
89 @NotNull
90 public PopupChooserBuilder setItemChoosenCallback(@NotNull Runnable runnable) {
91 myItemChoosenRunnable = runnable;
92 return this;
95 @NotNull
96 public PopupChooserBuilder setSouthComponent(@NotNull JComponent cmp) {
97 mySouthComponent = cmp;
98 return this;
102 public PopupChooserBuilder setRequestFocus(final boolean requestFocus) {
103 myRequestFocus = requestFocus;
104 return this;
107 public PopupChooserBuilder setResizable(final boolean forceResizable) {
108 myForceResizable = forceResizable;
109 return this;
113 public PopupChooserBuilder setMovable(final boolean forceMovable) {
114 myForceMovable = forceMovable;
115 return this;
118 public PopupChooserBuilder setDimensionServiceKey(@NonNls String key){
119 myDimensionServiceKey = key;
120 return this;
123 public PopupChooserBuilder setCancelCalllback(Computable<Boolean> callback) {
124 myCancelCallback = callback;
125 return this;
128 public PopupChooserBuilder setAlpha(final float alpha) {
129 myAlpha = alpha;
130 return this;
133 @NotNull
134 public JBPopup createPopup() {
135 JPanel contentPane = new JPanel(new BorderLayout());
136 if (!myForceMovable && myTitle != null) {
137 JLabel label = new JLabel(myTitle);
138 label.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
139 label.setHorizontalAlignment(SwingConstants.CENTER);
140 contentPane.add(label, BorderLayout.NORTH);
143 if (myChooserComponent instanceof MyListWrapper) {
144 JList list = ((MyListWrapper)myChooserComponent).myList;
145 if (list.getSelectedIndex() == -1 && myAutoselect) {
146 list.setSelectedIndex(0);
150 myChooserComponent.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
152 myChooserComponent.addMouseListener(new MouseAdapter() {
153 public void mouseClicked(MouseEvent e) {
154 if (!isSelectionButtonDown(e) && !e.isConsumed()) {
155 closePopup(true);
160 regsiterClosePopupKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), false);
161 regsiterClosePopupKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), true);
162 for (KeyStroke keystroke : myAdditionalKeystrokes) {
163 regsiterClosePopupKeyboardAction(keystroke, true);
166 final JScrollPane scrollPane;
167 if (myChooserComponent instanceof MyListWrapper) {
168 scrollPane = (MyListWrapper)myChooserComponent;
170 else if (myChooserComponent instanceof JTable) {
171 scrollPane = createScrollPane((JTable)myChooserComponent);
173 else if (myChooserComponent instanceof JTree) {
174 scrollPane = createScrollPane((JTree)myChooserComponent);
176 else {
177 throw new IllegalStateException("PopupChooserBuilder is intended to be constructed with one of JTable, JTree, JList components");
180 scrollPane.getViewport().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
181 contentPane.add(scrollPane, BorderLayout.CENTER);
183 if (mySouthComponent != null) {
184 contentPane.add(mySouthComponent, BorderLayout.SOUTH);
187 ComponentPopupBuilder builder = JBPopupFactory.getInstance()
188 .createComponentPopupBuilder(contentPane, myChooserComponent);
189 for (JBPopupListener each : myListeners) {
190 builder.addListener(each);
193 builder.setDimensionServiceKey(null, myDimensionServiceKey, false)
194 .setRequestFocus(myRequestFocus)
195 .setResizable(myForceResizable)
196 .setMovable(myForceMovable)
197 .setTitle(myForceMovable ? myTitle : null)
198 .setCancelCallback(myCancelCallback)
199 .setAlpha(myAlpha)
200 .setFocusOwners(myFocusOwners)
201 .setCancelKeyEnabled(myCancelKeyEnabled)
202 .setAdText(myAd);
204 if (myMinSize != null) {
205 builder.setMinSize(myMinSize);
208 myPopup = builder.createPopup();
209 return myPopup;
212 public PopupChooserBuilder setMinSize(final Dimension dimension) {
213 myMinSize = dimension;
214 return this;
217 private void regsiterClosePopupKeyboardAction(final KeyStroke keyStroke, final boolean shouldPerformAction) {
218 myChooserComponent.registerKeyboardAction(new AbstractAction() {
219 public void actionPerformed(ActionEvent e) {
220 closePopup(shouldPerformAction);
222 }, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
225 private void closePopup(boolean shouldPerformAction) {
226 myPopup.cancel();
228 if (shouldPerformAction) {
229 if (myItemChoosenRunnable != null) {
230 myItemChoosenRunnable.run();
235 @NotNull
236 public static JScrollPane createScrollPane(final JTable table) {
237 if (table instanceof TreeTable) {
238 TreeUtil.expandAll(((TreeTable)table).getTree());
241 JScrollPane scrollPane = new JScrollPane(table);
243 scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
245 if (table.getSelectedRow() == -1) {
246 table.getSelectionModel().setSelectionInterval(0, 0);
249 if (table.getRowCount() >= 20) {
250 scrollPane.getViewport().setPreferredSize(new Dimension(table.getPreferredScrollableViewportSize().width, 300));
252 else {
253 scrollPane.getViewport().setPreferredSize(table.getPreferredSize());
256 table.addMouseMotionListener(new MouseMotionAdapter() {
257 boolean myIsEngaged = false;
258 public void mouseMoved(MouseEvent e) {
259 if (myIsEngaged) {
260 int index = table.rowAtPoint(e.getPoint());
261 table.getSelectionModel().setSelectionInterval(index, index);
263 else {
264 myIsEngaged = true;
269 return scrollPane;
272 @NotNull
273 public static JScrollPane createScrollPane(final JTree tree) {
274 TreeUtil.expandAll(tree);
276 JScrollPane scrollPane = new JScrollPane(tree);
278 scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
280 if (tree.getSelectionCount() == 0) {
281 tree.setSelectionRow(0);
284 if (tree.getRowCount() >= 20) {
285 scrollPane.getViewport().setPreferredSize(new Dimension(tree.getPreferredScrollableViewportSize().width, 300));
287 else {
288 scrollPane.getViewport().setPreferredSize(tree.getPreferredSize());
291 tree.addMouseMotionListener(new MouseMotionAdapter() {
292 boolean myIsEngaged = false;
293 public void mouseMoved(MouseEvent e) {
294 if (myIsEngaged) {
295 final Point p = e.getPoint();
296 int index = tree.getRowForLocation(p.x, p.y);
297 tree.setSelectionRow(index);
299 else {
300 myIsEngaged = true;
305 return scrollPane;
308 public PopupChooserBuilder setAutoSelectIfEmpty(final boolean autoselect) {
309 myAutoselect = autoselect;
310 return this;
313 public PopupChooserBuilder setCancelKeyEnabled(final boolean enabled) {
314 myCancelKeyEnabled = enabled;
315 return this;
318 public PopupChooserBuilder addListener(final JBPopupListener listener) {
319 myListeners.add(listener);
320 return this;
323 private static boolean isSelectionButtonDown(MouseEvent e) {
324 return e.isShiftDown() || e.isControlDown() || e.isMetaDown();
327 private class MyListWrapper extends JScrollPane implements DataProvider {
328 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
329 private JList myList;
331 public MyListWrapper(final JList list) {
332 super(list);
333 list.addMouseMotionListener(new MouseMotionAdapter() {
334 boolean myIsEngaged = false;
335 public void mouseMoved(MouseEvent e) {
336 if (myIsEngaged && !isSelectionButtonDown(e)) {
337 Point point = e.getPoint();
338 int index = list.locationToIndex(point);
339 list.setSelectedIndex(index);
341 else {
342 myIsEngaged = true;
347 ListScrollingUtil.installActions(list);
349 int modelSize = list.getModel().getSize();
350 setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
351 if (modelSize > 0 && modelSize <= 20) {
352 list.setVisibleRowCount(0);
353 getViewport().setPreferredSize(list.getPreferredSize());
355 else {
356 list.setVisibleRowCount(20);
358 myList = list;
362 @Nullable
363 public Object getData(@NonNls String dataId) {
364 if (dataId.equals(DataConstants.SELECTED_ITEM)){
365 return myList.getSelectedValue();
367 return null;
370 public void setBorder(Border border) {
371 if (myList != null){
372 myList.setBorder(border);
376 public void requestFocus() {
377 myList.requestFocus();
380 public synchronized void addMouseListener(MouseListener l) {
381 myList.addMouseListener(l);
385 @NotNull
386 public PopupChooserBuilder setFocusOwners(@NotNull Component[] focusOwners) {
387 myFocusOwners = focusOwners;
388 return this;
391 @NotNull
392 public PopupChooserBuilder setAdText(String ad) {
393 myAd = ad;
394 return this;