replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / wizard / BindToExistingBeanStep.java
1 /*
2  * Copyright 2000-2009 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.wizard;
17
18 import com.intellij.ide.wizard.StepAdapter;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.ui.ComboBox;
21 import com.intellij.psi.PsiMethod;
22 import com.intellij.psi.PsiType;
23 import com.intellij.psi.util.PropertyUtil;
24 import com.intellij.uiDesigner.UIDesignerBundle;
25 import com.intellij.util.ArrayUtil;
26 import org.jetbrains.annotations.NonNls;
27 import org.jetbrains.annotations.NotNull;
28
29 import javax.swing.*;
30 import javax.swing.table.AbstractTableModel;
31 import javax.swing.table.TableCellEditor;
32 import javax.swing.table.TableColumn;
33 import java.awt.*;
34 import java.util.ArrayList;
35 import java.util.Collections;
36
37 /**
38  * @author Anton Katilin
39  * @author Vladimir Kondratyev
40  */
41 final class BindToExistingBeanStep extends StepAdapter{
42   private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.wizard.BindToExistingBeanStep");
43
44   private JScrollPane myScrollPane;
45   private JTable myTable;
46   private final WizardData myData;
47   private final MyTableModel myTableModel;
48   private JCheckBox myChkIsModified;
49   private JCheckBox myChkGetData;
50   private JCheckBox myChkSetData;
51   private JPanel myPanel;
52
53   BindToExistingBeanStep(@NotNull final WizardData data) {
54     myData = data;
55     myTableModel = new MyTableModel();
56     myTable.setModel(myTableModel);
57     myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
58     myTable.getColumnModel().setColumnSelectionAllowed(true);
59     myScrollPane.getViewport().setBackground(myTable.getBackground());
60     myTable.setSurrendersFocusOnKeystroke(true);
61
62     // Customize "Form Property" column
63     {
64       final TableColumn column = myTable.getColumnModel().getColumn(0/*Form Property*/);
65       column.setCellRenderer(new FormPropertyTableCellRenderer(myData.myProject));
66     }
67
68     // Customize "Bean Property" column
69     {
70       final TableColumn column = myTable.getColumnModel().getColumn(1/*Bean Property*/);
71       column.setCellRenderer(new BeanPropertyTableCellRenderer());
72       final MyTableCellEditor cellEditor = new MyTableCellEditor();
73       column.setCellEditor(cellEditor);
74
75       final DefaultCellEditor editor = (DefaultCellEditor)myTable.getDefaultEditor(Object.class);
76       editor.setClickCountToStart(1);
77
78       myTable.setRowHeight(cellEditor.myCbx.getPreferredSize().height);
79     }
80
81     myChkGetData.setSelected(true);
82     myChkGetData.setEnabled(false);
83     myChkSetData.setSelected(true);
84     myChkSetData.setEnabled(false);
85     myChkIsModified.setSelected(myData.myGenerateIsModified);
86   }
87
88   public JComponent getComponent() {
89     return myPanel;
90   }
91
92   public void _init() {
93     // Check that data is correct
94     LOG.assertTrue(!myData.myBindToNewBean);
95     LOG.assertTrue(myData.myBeanClass != null);
96     myTableModel.fireTableDataChanged();
97   }
98
99   public void _commit(boolean finishChosen) {
100     // Stop editing if any
101     final TableCellEditor cellEditor = myTable.getCellEditor();
102     if(cellEditor != null){
103       cellEditor.stopCellEditing();
104     }
105
106     myData.myGenerateIsModified = myChkIsModified.isSelected();
107
108     // TODO[vova] check that at least one binding field exists
109   }
110
111   private final class MyTableModel extends AbstractTableModel{
112     private final String[] myColumnNames;
113
114     public MyTableModel() {
115       myColumnNames = new String[]{
116         UIDesignerBundle.message("column.form.field"),
117         UIDesignerBundle.message("column.bean.property")};
118     }
119
120     public int getColumnCount() {
121       return myColumnNames.length;
122     }
123
124     public String getColumnName(final int column) {
125       return myColumnNames[column];
126     }
127
128     public int getRowCount() {
129       return myData.myBindings.length;
130     }
131
132     public boolean isCellEditable(final int row, final int column) {
133       return column == 1/*Bean Property*/;
134     }
135
136     public Object getValueAt(final int row, final int column) {
137       if(column == 0/*Form Property*/){
138         return myData.myBindings[row].myFormProperty;
139       }
140       else if(column == 1/*Bean Property*/){
141         return myData.myBindings[row].myBeanProperty;
142       }
143       else{
144         throw new IllegalArgumentException("unknown column: " + column);
145       }
146     }
147
148     public void setValueAt(final Object value, final int row, final int column) {
149       LOG.assertTrue(column == 1/*Bean Property*/);
150       final FormProperty2BeanProperty binding = myData.myBindings[row];
151       binding.myBeanProperty = (BeanProperty)value;
152     }
153   }
154
155   private final class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor{
156     private final ComboBox myCbx;
157     /* -1 if not defined*/
158     private int myEditingRow;
159
160     public MyTableCellEditor() {
161       myCbx = new ComboBox();
162       myCbx.setEditable(true);
163       myCbx.setRenderer(new BeanPropertyListCellRenderer());
164       myCbx.registerTableCellEditor(this);
165
166       final JComponent editorComponent = (JComponent)myCbx.getEditor().getEditorComponent();
167       editorComponent.setBorder(null);
168
169       myEditingRow = -1;
170     }
171
172     /**
173      * @return whether it's possible to convert {@code type1} into {@code type2}
174      * and vice versa.
175      */
176     private boolean canConvert(@NonNls final String type1, @NonNls final String type2){
177       if("boolean".equals(type1) || "boolean".equals(type2)){
178         return type1.equals(type2);
179       }
180       else{
181         return true;
182       }
183     }
184
185     public Component getTableCellEditorComponent(
186       final JTable table,
187       final Object value,
188       final boolean isSelected,
189       final int row,
190       final int column
191     ) {
192       myEditingRow = row;
193       final DefaultComboBoxModel model = (DefaultComboBoxModel)myCbx.getModel();
194       model.removeAllElements();
195       model.addElement(null/*<not defined>*/);
196
197       // Fill combobox with available bean's properties
198       final String[] rProps = PropertyUtil.getReadableProperties(myData.myBeanClass, true);
199       final String[] wProps = PropertyUtil.getWritableProperties(myData.myBeanClass, true);
200       final ArrayList<BeanProperty> rwProps = new ArrayList<>();
201
202       outer: for(int i = rProps.length - 1; i >= 0; i--){
203         final String propName = rProps[i];
204         if(ArrayUtil.find(wProps, propName) != -1){
205           LOG.assertTrue(!rwProps.contains(propName));
206           final PsiMethod getter = PropertyUtil.findPropertyGetter(myData.myBeanClass, propName, false, true);
207           if (getter == null) {
208             // possible if the getter is static: getReadableProperties() does not filter out static methods, and
209             // findPropertyGetter() checks for static/non-static
210             continue;
211           }
212           final PsiType returnType = getter.getReturnType();
213           LOG.assertTrue(returnType != null);
214
215           // There are two possible types: boolean and java.lang.String
216           @NonNls final String typeName = returnType.getCanonicalText();
217           LOG.assertTrue(typeName != null);
218           if(!"boolean".equals(typeName) && !"java.lang.String".equals(typeName)){
219             continue;
220           }
221
222           // Check that the property is not in use yet
223           for(int j = myData.myBindings.length - 1; j >= 0; j--){
224             final BeanProperty _property = myData.myBindings[j].myBeanProperty;
225             if(j != row && _property != null && propName.equals(_property.myName)){
226               continue outer;
227             }
228           }
229
230           // Check that we conver types
231           if(
232             !canConvert(
233               myData.myBindings[row].myFormProperty.getComponentPropertyClassName(),
234               typeName
235             )
236           ){
237             continue;
238           }
239
240           rwProps.add(new BeanProperty(propName, typeName));
241         }
242       }
243
244       Collections.sort(rwProps);
245
246       for (BeanProperty rwProp : rwProps) {
247         model.addElement(rwProp);
248       }
249
250       // Set initially selected item
251       if(myData.myBindings[row].myBeanProperty != null){
252         myCbx.setSelectedItem(myData.myBindings[row].myBeanProperty);
253       }
254       else{
255         myCbx.setSelectedIndex(0/*<not defined>*/);
256       }
257
258       return myCbx;
259     }
260
261     public Object getCellEditorValue() {
262       LOG.assertTrue(myEditingRow != -1);
263       try {
264         // our ComboBox is editable so its editor can contain:
265         // 1) BeanProperty object (it user just selected something from ComboBox)
266         // 2) java.lang.String if user type something into ComboBox
267
268         final Object selectedItem = myCbx.getEditor().getItem();
269         if(selectedItem instanceof BeanProperty){
270           return selectedItem;
271         }
272         else if(selectedItem instanceof String){
273           final String fieldName = ((String)selectedItem).trim();
274
275           if(fieldName.length() == 0){
276             return null; // binding is not defined
277           }
278
279           final String fieldType = myData.myBindings[myEditingRow].myFormProperty.getComponentPropertyClassName();
280           return new BeanProperty(fieldName, fieldType);
281         }
282         else{
283           throw new IllegalArgumentException("unknown selectedItem: " + selectedItem);
284         }
285       }
286       finally {
287         myEditingRow = -1; // unset editing row. So it's possible to invoke this method only once per editing
288       }
289     }
290   }
291 }