replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-api / src / com / intellij / ui / components / editors / JBComboBoxTableCellEditorComponent.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.ui.components.editors;
17
18 import com.intellij.ide.ui.AntialiasingType;
19 import com.intellij.openapi.ui.popup.JBPopup;
20 import com.intellij.openapi.ui.popup.JBPopupAdapter;
21 import com.intellij.openapi.ui.popup.JBPopupFactory;
22 import com.intellij.openapi.ui.popup.LightweightWindowEvent;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.ui.TableUtil;
25 import com.intellij.ui.awt.RelativePoint;
26 import com.intellij.ui.components.JBComboBoxLabel;
27 import com.intellij.ui.components.JBLabel;
28 import com.intellij.ui.components.JBList;
29 import com.intellij.ui.table.JBTable;
30 import com.intellij.util.Function;
31 import com.intellij.util.PlatformIcons;
32 import com.intellij.util.containers.ContainerUtil;
33 import com.intellij.util.ui.EmptyIcon;
34 import com.intellij.util.ui.GraphicsUtil;
35
36 import javax.accessibility.AccessibleContext;
37 import javax.accessibility.AccessibleRole;
38 import javax.accessibility.AccessibleState;
39 import javax.accessibility.AccessibleStateSet;
40 import javax.swing.*;
41 import javax.swing.event.TableModelEvent;
42 import java.awt.*;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.util.List;
46
47 /**
48  * Solves rendering problems in JTable components when JComboBox objects are used as cell
49  * editors components. Known issues of using JComboBox component are the following:
50  *   <p>1. Ugly view if row height is small enough
51  *   <p>2. Truncated strings in the combobox popup if column width is less than text value width
52  *   <p>
53  *   <b>How to use:</b>
54  *   <p>1. In get {@code getTableCellEditorComponent} method create or use existent
55  *   {@code JBComboBoxTableCellEditorComponent} instance<br/>
56  *   <p>2. Init component by calling {@code setCell}, {@code setOptions},
57  *   {@code setDefaultValue} methods
58  *   <p>3. Return the instance
59  *
60  * @author Konstantin Bulenkov
61  * @see JBComboBoxLabel
62  */
63 public class JBComboBoxTableCellEditorComponent extends JBLabel {
64   private JTable myTable;
65   private int myRow = 0;
66   private int myColumn = 0;
67   private final JBList myList = new JBList();
68   private Object[] myOptions = {};
69   private Object myValue;
70   public boolean myWide = false;
71   private Function<Object, String> myToString = StringUtil.createToStringFunction(Object.class);
72   private final List<ActionListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
73
74   @SuppressWarnings({"GtkPreferredJComboBoxRenderer"})
75   private ListCellRenderer myRenderer = new DefaultListCellRenderer() {
76     private boolean myChecked;
77     public Icon myEmptyIcon;
78
79     @Override
80     public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
81       final JLabel label = (JLabel)super.getListCellRendererComponent(list, myToString.fun(value), index, isSelected, cellHasFocus);
82       myChecked = (value == myValue);
83       if (myChecked) {
84         label.setIcon(getIcon(isSelected));
85       } else {
86         label.setIcon(getEmptyIcon());
87       }
88       GraphicsUtil.setAntialiasingType(label, AntialiasingType.getAAHintForSwingComponent());
89       return label;
90     }
91
92     private Icon getEmptyIcon() {
93       if (myEmptyIcon == null) {
94         myEmptyIcon = EmptyIcon.create(getIcon(true).getIconWidth());
95       }
96       return myEmptyIcon;
97     }
98
99     private Icon getIcon(boolean selected) {
100       final boolean small = "small".equals(JBComboBoxTableCellEditorComponent.this.getClientProperty("JComponent.sizeVariant"));
101       return small
102              ? selected ? PlatformIcons.CHECK_ICON_SMALL_SELECTED : PlatformIcons.CHECK_ICON_SMALL
103              : selected ? PlatformIcons.CHECK_ICON_SELECTED : PlatformIcons.CHECK_ICON;
104     }
105
106     @Override
107     public AccessibleContext getAccessibleContext() {
108       if (accessibleContext == null) {
109         accessibleContext = new AccessibleRenderer();
110       }
111       return accessibleContext;
112     }
113
114     class AccessibleRenderer extends AccessibleJLabel {
115       @Override
116       public AccessibleRole getAccessibleRole() {
117         return AccessibleRole.CHECK_BOX;
118       }
119
120       @Override
121       public AccessibleStateSet getAccessibleStateSet() {
122         AccessibleStateSet set = super.getAccessibleStateSet();
123         if (myChecked) {
124           set.add(AccessibleState.CHECKED);
125         }
126         return set;
127       }
128     }
129   };
130
131   public JBComboBoxTableCellEditorComponent() {
132   }
133
134   public JBComboBoxTableCellEditorComponent(JTable table) {
135     myTable = table;
136   }
137
138   public void setCell(JTable table, int row, int column) {
139     setTable(table);
140     setRow(row);
141     setColumn(column);
142   }
143
144   public void setTable(JTable table) {
145     myTable = table;
146   }
147
148   public void setRow(int row) {
149     myRow = row;
150   }
151
152   public void setColumn(int column) {
153     myColumn = column;
154   }
155
156   public Object[] getOptions() {
157     return myOptions;
158   }
159
160   public void setOptions(Object... options) {
161     myOptions = options;
162   }
163
164   @Override
165   public void addNotify() {
166     super.addNotify();
167     initAndShowPopup();
168   }
169
170   private void initAndShowPopup() {
171     myList.removeAll();
172     myList.setModel(JBList.createDefaultListModel(myOptions));
173     if (myRenderer != null) {
174       myList.setCellRenderer(myRenderer);
175     }
176     final Rectangle rect = myTable.getCellRect(myRow, myColumn, true);
177     Point point = new Point(rect.x, rect.y);
178     final boolean surrendersFocusOnKeystrokeOldValue = myTable instanceof JBTable ? ((JBTable)myTable).surrendersFocusOnKeyStroke() : myTable.getSurrendersFocusOnKeystroke();
179     final JBPopup popup = JBPopupFactory.getInstance()
180       .createListPopupBuilder(myList)
181       .setItemChoosenCallback(() -> {
182         myValue = myList.getSelectedValue();
183         final ActionEvent event = new ActionEvent(myList, ActionEvent.ACTION_PERFORMED, "elementChosen");
184         for (ActionListener listener : myListeners) {
185           listener.actionPerformed(event);
186         }
187         TableUtil.stopEditing(myTable);
188
189         myTable.setValueAt(myValue, myRow, myColumn); // on Mac getCellEditorValue() called before myValue is set.
190         myTable.tableChanged(new TableModelEvent(myTable.getModel(), myRow));  // force repaint
191       })
192       .setCancelCallback(() -> {
193         TableUtil.stopEditing(myTable);
194         return true;
195       })
196       .addListener(new JBPopupAdapter() {
197         @Override
198         public void beforeShown(LightweightWindowEvent event) {
199           super.beforeShown(event);
200           myTable.setSurrendersFocusOnKeystroke(false);
201         }
202
203         @Override
204         public void onClosed(LightweightWindowEvent event) {
205           myTable.setSurrendersFocusOnKeystroke(surrendersFocusOnKeystrokeOldValue);
206           super.onClosed(event);
207         }
208       })
209       .setMinSize(myWide ? new Dimension(((int)rect.getSize().getWidth()), -1) : null)
210       .createPopup();
211     popup.show(new RelativePoint(myTable, point));
212   }
213
214   public void setWide(boolean wide) {
215     this.myWide = wide;
216   }
217
218   public Object getEditorValue() {
219     return myValue;
220   }
221
222   public void setRenderer(ListCellRenderer renderer) {
223     myRenderer = renderer;
224   }
225
226   public void setDefaultValue(Object value) {
227     myValue = value;
228   }
229
230   public void setToString(Function<Object, String> toString) {
231     myToString = toString;
232   }
233
234   public void addActionListener(ActionListener listener) {
235     myListeners.add(listener);
236   }
237 }