replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / propertyInspector / editors / string / KeyChooserDialog.java
1 /*
2  * Copyright 2000-2014 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.uiDesigner.propertyInspector.editors.string;
17
18 import com.intellij.ide.DataManager;
19 import com.intellij.lang.properties.IProperty;
20 import com.intellij.lang.properties.psi.PropertiesFile;
21 import com.intellij.openapi.actionSystem.CommonDataKeys;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.ui.DialogWrapper;
24 import com.intellij.openapi.util.Couple;
25 import com.intellij.openapi.util.DimensionService;
26 import com.intellij.ui.DoubleClickListener;
27 import com.intellij.ui.ScrollPaneFactory;
28 import com.intellij.ui.SpeedSearchBase;
29 import com.intellij.ui.table.JBTable;
30 import com.intellij.uiDesigner.UIDesignerBundle;
31 import com.intellij.uiDesigner.designSurface.GuiEditor;
32 import com.intellij.uiDesigner.lw.StringDescriptor;
33 import gnu.trove.TObjectIntHashMap;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import javax.swing.*;
39 import javax.swing.table.AbstractTableModel;
40 import javax.swing.table.TableCellRenderer;
41 import javax.swing.table.TableColumn;
42 import javax.swing.table.TableColumnModel;
43 import java.awt.*;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.KeyEvent;
46 import java.awt.event.MouseEvent;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.Comparator;
50 import java.util.List;
51
52 /**
53  * @author Anton Katilin
54  * @author Vladimir Kondratyev
55  */
56 public final class KeyChooserDialog extends DialogWrapper{
57   private final PropertiesFile myBundle;
58   private final String myBundleName;
59   /** List of bundle's pairs*/
60   private ArrayList<Couple<String>> myPairs;
61   private final JComponent myCenterPanel;
62   /** Table with key/value pairs */
63   private final JTable myTable;
64   @NonNls private static final String NULL = "null";
65   private final MyTableModel myModel;
66   private final GuiEditor myEditor;
67
68   private static final String OK_ACTION = "OkAction";
69
70   /**
71    * @param bundle resource bundle to be shown.
72    * @param bundleName name of the resource bundle to be shown. We need this
73    * name to create StringDescriptor in {@link #getDescriptor()} method.
74    * @param keyToPreselect describes row that should be selected in the
75    * @param parent the parent component for the dialog.
76    */
77   public KeyChooserDialog(
78     final Component parent,
79     @NotNull final PropertiesFile bundle,
80     @NotNull final String bundleName,
81     final String keyToPreselect,
82     final GuiEditor editor
83   ) {
84     super(parent, true);
85     myEditor = editor;
86     myBundle = bundle;
87
88     myBundleName = bundleName;
89
90     setTitle(UIDesignerBundle.message("title.chooser.value"));
91
92     // Read key/value pairs from resource bundle
93     fillPropertyList();
94
95     // Create UI
96     myModel = new MyTableModel();
97     myTable = new JBTable(myModel);
98     myTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
99     new MySpeedSearch(myTable);
100     myCenterPanel = ScrollPaneFactory.createScrollPane(myTable);
101
102     myTable.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), OK_ACTION);
103     myTable.getActionMap().put(OK_ACTION, new AbstractAction() {
104       public void actionPerformed(ActionEvent e) {
105         getOKAction().actionPerformed(e);
106       }
107     });
108
109     // Calculate width for "Key" columns
110     final Project projectGuess = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parent));
111     final Dimension size = DimensionService.getInstance().getSize(getDimensionServiceKey(), projectGuess);
112     final FontMetrics metrics = myTable.getFontMetrics(myTable.getFont());
113     int minWidth = 200;
114     int maxWidth = size != null ? size.width / 2 : Integer.MAX_VALUE;
115     if (minWidth > maxWidth) {
116       minWidth = maxWidth;
117     }
118     int width = minWidth;
119     for(int i = myPairs.size() - 1; i >= 0; i--){
120       final Couple<String> pair = myPairs.get(i);
121       width = Math.max(width, metrics.stringWidth(pair.getFirst()));
122     }
123     width += 20;
124     width = Math.max(width, metrics.stringWidth(myModel.getColumnName(0)));
125     width = Math.max(width, minWidth);
126     width = Math.min(width, maxWidth);
127     final TableColumnModel columnModel = myTable.getColumnModel();
128     final TableColumn keyColumn = columnModel.getColumn(0);
129     keyColumn.setMaxWidth(width);
130     keyColumn.setMinWidth(width);
131     final TableCellRenderer defaultRenderer = myTable.getDefaultRenderer(String.class);
132     if (defaultRenderer instanceof JComponent) {
133       final JComponent component = (JComponent)defaultRenderer;
134       component.putClientProperty("html.disable", Boolean.TRUE);
135     }
136     selectKey(keyToPreselect);
137
138     init();
139     new DoubleClickListener() {
140       @Override
141       protected boolean onDoubleClick(MouseEvent e) {
142         doOKAction();
143         return true;
144       }
145     }.installOn(myTable);
146   }
147
148   private void fillPropertyList() {
149     myPairs = new ArrayList<>();
150
151     final List<IProperty> properties = myBundle.getProperties();
152     for (IProperty property : properties) {
153       final String key = property.getUnescapedKey();
154       final String value = property.getValue();
155       if (key != null) {
156         myPairs.add(Couple.of(key, value != null ? value : NULL));
157       }
158     }
159     Collections.sort(myPairs, new MyPairComparator());
160   }
161
162   private void selectKey(final String keyToPreselect) {
163     // Preselect proper row
164     int indexToPreselect = -1;
165     for(int i = myPairs.size() - 1; i >= 0; i--){
166       final Couple<String> pair = myPairs.get(i);
167       if(pair.getFirst().equals(keyToPreselect)){
168         indexToPreselect = i;
169         break;
170       }
171     }
172     if(indexToPreselect != -1){
173       selectElementAt(indexToPreselect);
174     }
175   }
176
177   @NotNull
178   @Override protected Action[] createLeftSideActions() {
179     return new Action[] { new NewKeyValueAction() };
180   }
181
182   private void selectElementAt(final int index) {
183     myTable.getSelectionModel().setSelectionInterval(index, index);
184     myTable.scrollRectToVisible(myTable.getCellRect(index, 0, true));
185   }
186
187   @NotNull
188   protected String getDimensionServiceKey() {
189     return getClass().getName();
190   }
191
192   public JComponent getPreferredFocusedComponent() {
193     return myTable;
194   }
195
196   /**
197    * @return resolved string descriptor. If user chose nothing then the
198    * method returns {@code null}.
199    */
200   @Nullable StringDescriptor getDescriptor() {
201     final int selectedRow = myTable.getSelectedRow();
202     if(selectedRow < 0 || selectedRow >= myTable.getRowCount()){
203       return null;
204     }
205     else{
206       final Couple<String> pair = myPairs.get(selectedRow);
207       final StringDescriptor descriptor = new StringDescriptor(myBundleName, pair.getFirst());
208       descriptor.setResolvedValue(pair.getSecond());
209       return descriptor;
210     }
211   }
212
213   protected JComponent createCenterPanel() {
214     return myCenterPanel;
215   }
216
217   private static final class MyPairComparator implements Comparator<Couple<String>>{
218     public int compare(final Couple<String> p1, final Couple<String> p2) {
219       return p1.getFirst().compareToIgnoreCase(p2.getFirst());
220     }
221   }
222
223   private final class MyTableModel extends AbstractTableModel{
224     public int getColumnCount() {
225       return 2;
226     }
227
228     public String getColumnName(final int column) {
229       if(column == 0){
230         return UIDesignerBundle.message("column.key");
231       }
232       else if(column == 1){
233         return UIDesignerBundle.message("column.value");
234       }
235       else{
236         throw new IllegalArgumentException("unknown column: " + column);
237       }
238     }
239
240     public Class getColumnClass(final int column) {
241       if(column == 0){
242         return String.class;
243       }
244       else if(column == 1){
245         return String.class;
246       }
247       else{
248         throw new IllegalArgumentException("unknown column: " + column);
249       }
250     }
251
252     public Object getValueAt(final int row, final int column) {
253       if(column == 0){
254         return myPairs.get(row).getFirst();
255       }
256       else if(column == 1){
257         return myPairs.get(row).getSecond();
258       }
259       else{
260         throw new IllegalArgumentException("unknown column: " + column);
261       }
262     }
263
264     public int getRowCount() {
265       return myPairs.size();
266     }
267
268     public void update() {
269       fireTableDataChanged();
270     }
271   }
272
273   private class MySpeedSearch extends SpeedSearchBase<JTable> {
274     private TObjectIntHashMap<Object> myElements;
275     private Object[] myElementsArray;
276
277     public MySpeedSearch(final JTable component) {
278       super(component);
279     }
280
281     @Override
282     protected int convertIndexToModel(int viewIndex) {
283       return getComponent().convertRowIndexToModel(viewIndex);
284     }
285
286     public int getSelectedIndex() {
287       return myComponent.getSelectedRow();
288     }
289
290     public Object[] getAllElements() {
291       if (myElements == null) {
292         myElements = new TObjectIntHashMap<>();
293         myElementsArray = myPairs.toArray();
294         for (int idx = 0; idx < myElementsArray.length; idx++) {
295           Object element = myElementsArray[idx];
296           myElements.put(element, idx);
297         }
298       }
299       return myElementsArray;
300     }
301
302     public String getElementText(final Object element) {
303       //noinspection unchecked
304       return ((Couple<String>)element).getFirst();
305     }
306
307     public void selectElement(final Object element, final String selectedText) {
308       final int index = myElements.get(element);
309       selectElementAt(getComponent().convertRowIndexToView(index));
310     }
311   }
312
313   private class NewKeyValueAction extends AbstractAction {
314     public NewKeyValueAction() {
315       putValue(Action.NAME, UIDesignerBundle.message("key.chooser.new.property"));
316     }
317
318     public void actionPerformed(ActionEvent e) {
319       NewKeyDialog dlg = new NewKeyDialog(getWindow());
320       if (dlg.showAndGet()) {
321         if (!StringEditorDialog.saveCreatedProperty(myBundle, dlg.getName(), dlg.getValue(), myEditor.getPsiFile())) return;
322
323         fillPropertyList();
324         myModel.update();
325         selectKey(dlg.getName());
326       }
327     }
328   }
329 }