replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / propertyInspector / PropertyInspectorTable.java
1 /*
2  * Copyright 2000-2016 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;
17
18 import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.ide.ui.LafManager;
21 import com.intellij.ide.ui.LafManagerListener;
22 import com.intellij.lang.annotation.HighlightSeverity;
23 import com.intellij.openapi.actionSystem.*;
24 import com.intellij.openapi.command.CommandProcessor;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.colors.EditorColorsManager;
27 import com.intellij.openapi.editor.colors.TextAttributesKey;
28 import com.intellij.openapi.editor.markup.TextAttributes;
29 import com.intellij.openapi.module.Module;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.Messages;
32 import com.intellij.openapi.util.Comparing;
33 import com.intellij.openapi.util.Ref;
34 import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
35 import com.intellij.psi.JavaPsiFacade;
36 import com.intellij.psi.PsiClass;
37 import com.intellij.psi.PsiManager;
38 import com.intellij.psi.PsiMethod;
39 import com.intellij.psi.search.GlobalSearchScope;
40 import com.intellij.psi.util.PropertyUtil;
41 import com.intellij.ui.*;
42 import com.intellij.uiDesigner.ErrorAnalyzer;
43 import com.intellij.uiDesigner.ErrorInfo;
44 import com.intellij.uiDesigner.Properties;
45 import com.intellij.uiDesigner.UIDesignerBundle;
46 import com.intellij.uiDesigner.actions.ShowJavadocAction;
47 import com.intellij.uiDesigner.componentTree.ComponentTree;
48 import com.intellij.uiDesigner.designSurface.GuiEditor;
49 import com.intellij.uiDesigner.palette.Palette;
50 import com.intellij.uiDesigner.propertyInspector.properties.*;
51 import com.intellij.uiDesigner.radComponents.*;
52 import com.intellij.util.ui.*;
53 import icons.UIDesignerIcons;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import javax.swing.event.ChangeEvent;
60 import javax.swing.plaf.TableUI;
61 import javax.swing.table.AbstractTableModel;
62 import javax.swing.table.TableCellEditor;
63 import javax.swing.table.TableCellRenderer;
64 import java.awt.*;
65 import java.awt.event.ActionEvent;
66 import java.awt.event.KeyEvent;
67 import java.awt.event.MouseAdapter;
68 import java.awt.event.MouseEvent;
69 import java.lang.reflect.InvocationTargetException;
70 import java.util.*;
71 import java.util.List;
72
73 /**
74  * @author Anton Katilin
75  * @author Vladimir Kondratyev
76  */
77 public final class PropertyInspectorTable extends Table implements DataProvider{
78   private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.propertyInspector.PropertyInspectorTable");
79
80   public static final DataKey<PropertyInspectorTable> DATA_KEY = DataKey.create(PropertyInspectorTable.class.getName());
81
82   private static final Color SYNTETIC_PROPERTY_BACKGROUND = new JBColor(Gray._230, UIUtil.getPanelBackground().brighter());
83   private static final Color SYNTETIC_SUBPROPERTY_BACKGROUND = new JBColor(Gray._240, UIUtil.getPanelBackground().brighter());
84
85   private final ComponentTree myComponentTree;
86   private final ArrayList<Property> myProperties;
87   private final MyModel myModel;
88   private final MyCompositeTableCellRenderer myCellRenderer;
89   private final MyCellEditor myCellEditor;
90   private GuiEditor myEditor;
91   /**
92    * This listener gets notifications from current property editor
93    */
94   private final MyPropertyEditorListener myPropertyEditorListener;
95   /**
96    * Updates UIs of synthetic properties
97    */
98   private final MyLafManagerListener myLafManagerListener;
99   /**
100    * This is property exists in this map then it's expanded.
101    * It means that its children is visible.
102    */
103   private final HashSet<String> myExpandedProperties;
104   /**
105    * Component to be edited
106    */
107   @NotNull private final List<RadComponent> mySelection = new ArrayList<>();
108   /**
109    * If true then inspector will show "expert" properties
110    */
111   private boolean myShowExpertProperties;
112
113   private final Map<HighlightSeverity, SimpleTextAttributes> myHighlightAttributes = new HashMap<>();
114   private final Map<HighlightSeverity, SimpleTextAttributes> myModifiedHighlightAttributes = new HashMap<>();
115
116   private final ClassToBindProperty myClassToBindProperty;
117   private final BindingProperty myBindingProperty;
118   private final BorderProperty myBorderProperty;
119   private final LayoutManagerProperty myLayoutManagerProperty = new LayoutManagerProperty();
120   private final ButtonGroupProperty myButtonGroupProperty = new ButtonGroupProperty();
121
122   private boolean myInsideSynch;
123   private boolean myStoppingEditing;
124   private final Project myProject;
125
126   @NonNls private static final String ourHelpID = "guiDesigner.uiTour.inspector";
127
128   PropertyInspectorTable(Project project, @NotNull final ComponentTree componentTree) {
129     myProject = project;
130     myClassToBindProperty = new ClassToBindProperty(project);
131     myBindingProperty = new BindingProperty(project);
132     myBorderProperty = new BorderProperty(project);
133
134     myPropertyEditorListener = new MyPropertyEditorListener();
135     myLafManagerListener = new MyLafManagerListener();
136     myComponentTree=componentTree;
137     myProperties = new ArrayList<>();
138     myExpandedProperties = new HashSet<>();
139     myModel = new MyModel();
140     setModel(myModel);
141     setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
142
143     myCellRenderer = new MyCompositeTableCellRenderer();
144     myCellEditor = new MyCellEditor();
145
146     addMouseListener(new MouseAdapter() {
147       @Override
148       public void mousePressed(final MouseEvent e){
149         final int row = rowAtPoint(e.getPoint());
150         final int column = columnAtPoint(e.getPoint());
151         if (row == -1){
152           return;
153         }
154         final Property property = myProperties.get(row);
155         int indent = getPropertyIndentDepth(property) * getPropertyIndentWidth();
156         final Rectangle rect = getCellRect(row, convertColumnIndexToView(0), false);
157
158         Component rendererComponent = myCellRenderer.getTableCellRendererComponent(PropertyInspectorTable.this,
159                                                                                    property, false, false, row, column);
160         if (!rect.contains(e.getX(), e.getY()) ||
161             !(rendererComponent instanceof ColoredTableCellRenderer) ||
162             ((ColoredTableCellRenderer)rendererComponent).findFragmentAt(e.getX()) != SimpleColoredComponent.FRAGMENT_ICON ||
163             e.getX() < rect.x + indent) {
164           return;
165         }
166
167         final Property[] children = getPropChildren(property);
168         if (children.length == 0) {
169           return;
170         }
171
172         if (isPropertyExpanded(property)) {
173           collapseProperty(row);
174         }
175         else {
176           expandProperty(row);
177         }
178       }
179     });
180
181     new DoubleClickListener() {
182       @Override
183       protected boolean onDoubleClick(MouseEvent e) {
184         int row = rowAtPoint(e.getPoint());
185         int column = columnAtPoint(e.getPoint());
186         if (row >= 0 && column == 0) {
187           final Property property = myProperties.get(row);
188           if (getPropChildren(property).length == 0) {
189             startEditing(row);
190             return true;
191           }
192         }
193         return false;
194       }
195     }.installOn(this);
196
197
198     final AnAction quickJavadocAction = ActionManager.getInstance().getAction(IdeActions.ACTION_QUICK_JAVADOC);
199     new ShowJavadocAction().registerCustomShortcutSet(
200       quickJavadocAction.getShortcutSet(), this
201     );
202
203     // Popup menu
204     PopupHandler.installPopupHandler(
205       this,
206       (ActionGroup)ActionManager.getInstance().getAction(IdeActions.GROUP_GUI_DESIGNER_PROPERTY_INSPECTOR_POPUP),
207       ActionPlaces.GUI_DESIGNER_PROPERTY_INSPECTOR_POPUP, ActionManager.getInstance());
208   }
209
210   public void setEditor(final GuiEditor editor) {
211     finishEditing();
212     myEditor = editor;
213     if (myEditor == null) {
214       mySelection.clear();
215       myProperties.clear();
216       myModel.fireTableDataChanged();
217     }
218   }
219
220   /**
221    * @return currently selected {@link IntrospectedProperty} or {@code null}
222    * if nothing selected or synthetic property is selected.
223    */
224   @Nullable
225   public IntrospectedProperty getSelectedIntrospectedProperty(){
226     Property property = getSelectedProperty();
227     if (property == null || !(property instanceof IntrospectedProperty)) {
228       return null;
229     }
230
231     return (IntrospectedProperty)property;
232   }
233
234   @Nullable public Property getSelectedProperty() {
235     final int selectedRow = getSelectedRow();
236     if(selectedRow < 0 || selectedRow >= getRowCount()){
237       return null;
238     }
239
240     return myProperties.get(selectedRow);
241   }
242
243   /**
244    * @return {@link PsiClass} of the component which properties are displayed inside the inspector
245    */
246   public PsiClass getComponentClass(){
247     final Module module = myEditor.getModule();
248
249     if (mySelection.size() == 0) return null;
250     String className = mySelection.get(0).getComponentClassName();
251     for(int i=1; i<mySelection.size(); i++) {
252       if (!Comparing.equal(mySelection.get(i).getComponentClassName(), className)) {
253         return null;
254       }
255     }
256
257     return JavaPsiFacade.getInstance(module.getProject())
258       .findClass(className, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module));
259   }
260
261   @Override
262   public Object getData(final String dataId) {
263     if(getClass().getName().equals(dataId)){
264       return this;
265     }
266     else if(CommonDataKeys.PSI_ELEMENT.is(dataId)){
267       final IntrospectedProperty introspectedProperty = getSelectedIntrospectedProperty();
268       if(introspectedProperty == null){
269         return null;
270       }
271       final PsiClass aClass = getComponentClass();
272       if(aClass == null){
273         return null;
274       }
275
276       final PsiMethod getter = PropertyUtil.findPropertyGetter(aClass, introspectedProperty.getName(), false, true);
277       if(getter != null){
278         return getter;
279       }
280
281       return PropertyUtil.findPropertySetter(aClass, introspectedProperty.getName(), false, true);
282     }
283     else if (CommonDataKeys.PSI_FILE.is(dataId) && myEditor != null) {
284       return PsiManager.getInstance(myEditor.getProject()).findFile(myEditor.getFile());
285     }
286     else if (GuiEditor.DATA_KEY.is(dataId)) {
287       return myEditor;
288     }
289     else if (PlatformDataKeys.FILE_EDITOR.is(dataId)) {
290       GuiEditor designer = myProject.isDisposed() ? null : DesignerToolWindowManager.getInstance(myProject).getActiveFormEditor();
291       return designer == null ? null : designer.getEditor();
292     }
293     else if (PlatformDataKeys.HELP_ID.is(dataId)) {
294       return ourHelpID;
295     }
296     else {
297       return null;
298     }
299   }
300
301   /**
302    * Sets whenther "expert" properties are shown or not
303    */
304   void setShowExpertProperties(final boolean showExpertProperties){
305     if(myShowExpertProperties == showExpertProperties){
306       return;
307     }
308     myShowExpertProperties = showExpertProperties;
309     if (myEditor != null) {
310       synchWithTree(true);
311     }
312   }
313
314   @Override
315   public void addNotify() {
316     super.addNotify();
317     LafManager.getInstance().addLafManagerListener(myLafManagerListener);
318   }
319
320   @Override
321   public void removeNotify() {
322     LafManager.getInstance().removeLafManagerListener(myLafManagerListener);
323     super.removeNotify();
324   }
325
326   /**
327    * Standard JTable's UI has non convenient keybinding for
328    * editing. Therefore we have to replace some standard actions.
329    */
330   @Override
331   public void setUI(final TableUI ui){
332     super.setUI(ui);
333
334     // Customize action and input maps
335     @NonNls final ActionMap actionMap=getActionMap();
336     @NonNls final InputMap focusedInputMap=getInputMap(JComponent.WHEN_FOCUSED);
337     @NonNls final InputMap ancestorInputMap=getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
338
339     actionMap.put("selectPreviousRow",new MySelectPreviousRowAction());
340
341     actionMap.put("selectNextRow",new MySelectNextRowAction());
342
343     actionMap.put("startEditing",new MyStartEditingAction());
344     focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F2,0),"startEditing");
345     ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_F2,0));
346
347     actionMap.put("smartEnter",new MyEnterAction());
348     focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0),"smartEnter");
349     ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
350
351     focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0),"cancel");
352     ancestorInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0),"cancel");
353
354     actionMap.put("expandCurrent", new MyExpandCurrentAction(true));
355     focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD,0),"expandCurrent");
356     ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_ADD,0));
357
358     actionMap.put("collapseCurrent", new MyExpandCurrentAction(false));
359     focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT,0),"collapseCurrent");
360     ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT,0));
361   }
362
363   @Override
364   public void setValueAt(final Object aValue, final int row, final int column) {
365     final Property property = myProperties.get(row);
366     super.setValueAt(aValue, row, column);
367     // We need to repaint whole inspector because change of one property
368     // might causes change of another property.
369     if (property.needRefreshPropertyList()) {
370       synchWithTree(true);
371     }
372     repaint();
373   }
374
375   /**
376    * Gets first selected component from ComponentTree and sets it for editing.
377    * The method tries to keep selection in the list, so if new component has the property
378    * which is already selected then the new value will be
379    * also selected. It is very convenient.
380    *
381    * @param forceSynch if {@code false} and selected component in the ComponentTree
382    * is the same as current component in the PropertyInspector then method does
383    * nothing such sace. If {@code true} then inspector is forced to resynch.
384    */
385   public void synchWithTree(final boolean forceSynch){
386     if (myInsideSynch) {
387       return;
388     }
389     myInsideSynch = true;
390     try {
391       RadComponent[] newSelection = myComponentTree.getSelectedComponents();
392       if (!forceSynch && mySelection.size() == newSelection.length) {
393         boolean anyChanges = false;
394         for(RadComponent c: newSelection) {
395           if (!mySelection.contains(c)) {
396             anyChanges = true;
397             break;
398           }
399         }
400         if (!anyChanges) return;
401       }
402
403       mySelection.clear();
404       Collections.addAll(mySelection, newSelection);
405
406       if (isEditing()){
407         cellEditor.stopCellEditing();
408       }
409
410       // Store selected property
411       final int selectedRow=getSelectedRow();
412       Property selectedProperty=null;
413       if(selectedRow >= 0 && selectedRow < myProperties.size()){
414         selectedProperty=myProperties.get(selectedRow);
415       }
416
417       collectPropertiesForSelection();
418       myModel.fireTableDataChanged();
419
420       // Try to restore selection
421       final ArrayList<Property> reversePath= new ArrayList<>(2);
422       while(selectedProperty!=null){
423         reversePath.add(selectedProperty);
424         selectedProperty=selectedProperty.getParent();
425       }
426       int indexToSelect=-1;
427       for(int i=reversePath.size()-1;i>=0;i--){
428         final Property property=reversePath.get(i);
429         int index=findPropertyByName(myProperties, property.getName());
430         if(index==-1 && indexToSelect!=-1){ // try to expand parent and try again
431           expandProperty(indexToSelect);
432           index=findPropertyByName(myProperties, property.getName());
433           if(index!=-1){
434             indexToSelect=index;
435           }else{
436             break;
437           }
438         }else{
439           indexToSelect=index;
440         }
441       }
442
443       if(indexToSelect!=-1){
444         getSelectionModel().setSelectionInterval(indexToSelect,indexToSelect);
445       }else if(getRowCount()>0){
446         // Select first row if it's impossible to restore selection
447         getSelectionModel().setSelectionInterval(0,0);
448       }
449       TableUtil.scrollSelectionToVisible(this);
450     }
451     finally {
452       myInsideSynch = false;
453     }
454   }
455
456   private void collectPropertiesForSelection() {
457     myProperties.clear();
458     if (mySelection.size() > 0) {
459       collectProperties(mySelection.get(0), myProperties);
460
461       for(int propIndex=myProperties.size()-1; propIndex >= 0; propIndex--) {
462         if (!myProperties.get(propIndex).appliesToSelection(mySelection)) {
463           myProperties.remove(propIndex);
464         }
465       }
466
467       for(int i=1; i<mySelection.size(); i++) {
468         ArrayList<Property> otherProperties = new ArrayList<>();
469         collectProperties(mySelection.get(i), otherProperties);
470         for(int propIndex=myProperties.size()-1; propIndex >= 0; propIndex--) {
471           final Property prop = myProperties.get(propIndex);
472           int otherPropIndex = findPropertyByName(otherProperties, prop.getName());
473           if (otherPropIndex < 0) {
474             myProperties.remove(propIndex);
475             continue;
476           }
477           final Property otherProp = otherProperties.get(otherPropIndex);
478           if (!otherProp.getClass().equals(prop.getClass())) {
479             myProperties.remove(propIndex);
480             continue;
481           }
482           Property[] children = prop.getChildren(mySelection.get(0));
483           Property[] otherChildren = otherProp.getChildren(mySelection.get(i));
484           if (children.length != otherChildren.length) {
485             myProperties.remove(propIndex);
486             continue;
487           }
488           for(int childIndex=0; childIndex<children.length; childIndex++) {
489             if (!Comparing.equal(children [childIndex].getName(), otherChildren [childIndex].getName())) {
490               myProperties.remove(propIndex);
491               break;
492             }
493           }
494         }
495       }
496     }
497   }
498
499   /**
500    * @return index of the property with specified {@code name}.
501    * If there is no such property then the method returns {@code -1}.
502    */
503   private static int findPropertyByName(final ArrayList<Property> properties, final String name){
504     for(int i=properties.size()-1;i>=0;i--){
505       final Property property=properties.get(i);
506       if(property.getName().equals(name)){
507         return i;
508       }
509     }
510     return -1;
511   }
512
513   /**
514    * Populates result list with the properties available for the specified
515    * component
516    */
517   private void collectProperties(final RadComponent component, final ArrayList<Property> result) {
518     if (component instanceof RadRootContainer){
519       addProperty(result, myClassToBindProperty);
520     }
521     else {
522       if (!(component instanceof RadVSpacer || component instanceof RadHSpacer)){
523         addProperty(result, myBindingProperty);
524         addProperty(result, CustomCreateProperty.getInstance(myProject));
525       }
526
527       if(component instanceof RadContainer){
528         RadContainer container = (RadContainer) component;
529         if (container.getLayoutManager().getName() != null) {
530           addProperty(result, myLayoutManagerProperty);
531         }
532         addProperty(result, myBorderProperty);
533
534         final Property[] containerProperties = container.getLayoutManager().getContainerProperties(myProject);
535         addApplicableProperties(containerProperties, container, result);
536       }
537
538       final RadContainer parent = component.getParent();
539       if (parent != null) {
540         final Property[] properties = parent.getLayoutManager().getComponentProperties(myProject, component);
541         addApplicableProperties(properties, component, result);
542       }
543
544       if (component.getDelegee() instanceof AbstractButton &&
545           !(component.getDelegee() instanceof JButton)) {
546         addProperty(result, myButtonGroupProperty);
547       }
548       if (!(component instanceof RadVSpacer || component instanceof RadHSpacer)) {
549         addProperty(result, ClientPropertiesProperty.getInstance(myProject));
550       }
551
552       if (component.hasIntrospectedProperties()) {
553         final Class componentClass = component.getComponentClass();
554         final IntrospectedProperty[] introspectedProperties =
555           Palette.getInstance(myEditor.getProject()).getIntrospectedProperties(component);
556         final Properties properties = Properties.getInstance();
557         for (final IntrospectedProperty property: introspectedProperties) {
558           if (!property.appliesTo(component)) continue;
559           if (!myShowExpertProperties && properties.isExpertProperty(component.getModule(), componentClass, property.getName()) &&
560             !isModifiedForSelection(property)) {
561             continue;
562           }
563           addProperty(result, property);
564         }
565       }
566     }
567   }
568
569   private void addApplicableProperties(final Property[] containerProperties,
570                                        final RadComponent component,
571                                        final ArrayList<Property> result) {
572     for(Property prop: containerProperties) {
573       //noinspection unchecked
574       if (prop.appliesTo(component)) {
575         addProperty(result, prop);
576       }
577     }
578   }
579
580   private void addProperty(final ArrayList<Property> result, final Property property) {
581     result.add(property);
582     if (isPropertyExpanded(property)) {
583       for(Property child: getPropChildren(property)) {
584         addProperty(result, child);
585       }
586     }
587   }
588
589   private boolean isPropertyExpanded(final Property property) {
590     return myExpandedProperties.contains(getDottedName(property));
591   }
592
593   private static String getDottedName(final Property property) {
594     final Property parent = property.getParent();
595     if (parent != null) {
596       return parent.getName() + "." + property.getName();
597     }
598     return property.getName();
599   }
600
601   private static int getPropertyIndentDepth(final Property property) {
602     final Property parent = property.getParent();
603     if (parent != null) {
604       return parent.getParent() != null ? 2 : 1;
605     }
606     return 0;
607   }
608
609   private static int getPropertyIndentWidth() {
610     return JBUI.scale(11);
611   }
612
613   private Property[] getPropChildren(final Property property) {
614     return property.getChildren(mySelection.get(0));
615   }
616
617   @Override
618   public TableCellEditor getCellEditor(final int row, final int column){
619     final PropertyEditor editor = myProperties.get(row).getEditor();
620     editor.removePropertyEditorListener(myPropertyEditorListener); // we do not need to add listener on every invocation
621     editor.addPropertyEditorListener(myPropertyEditorListener);
622     myCellEditor.setEditor(editor);
623     return myCellEditor;
624   }
625
626   @Override
627   public TableCellRenderer getCellRenderer(final int row, final int column){
628     return myCellRenderer;
629   }
630
631   /*
632    * This method is overriden due to bug in the JTree. The problem is that
633    * JTree does not properly repaint edited cell if the editor is opaque or
634    * has opaque child components.
635    */
636   @Override
637   public boolean editCellAt(final int row, final int column, final EventObject e){
638     final boolean result = super.editCellAt(row, column, e);
639     final Rectangle cellRect = getCellRect(row, column, true);
640     repaint(cellRect);
641     return result;
642   }
643
644   /**
645    * Starts editing property with the specified {@code index}.
646    * The method does nothing is property isn't editable.
647    */
648   private void startEditing(final int index){
649     final Property property=myProperties.get(index);
650     final PropertyEditor editor=property.getEditor();
651     if(editor==null){
652       return;
653     }
654     editCellAt(index,convertColumnIndexToView(1));
655     LOG.assertTrue(editorComp!=null);
656     // Now we have to request focus into the editor component
657     JComponent prefComponent = editor.getPreferredFocusedComponent((JComponent)editorComp);
658     if(prefComponent == null){ // use default policy to find preferred focused component
659       prefComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent((JComponent)editorComp);
660     }
661     if (prefComponent != null) {
662       prefComponent.requestFocusInWindow();
663     }
664   }
665
666   private void finishEditing(){
667     if(editingRow==-1){
668       return;
669     }
670     editingStopped(new ChangeEvent(cellEditor));
671   }
672
673   @Override
674   public void editingStopped(final ChangeEvent ignored){
675     LOG.assertTrue(isEditing());
676     LOG.assertTrue(editingRow!=-1);
677     if (myStoppingEditing) {
678       return;
679     }
680     myStoppingEditing = true;
681     final Property property=myProperties.get(editingRow);
682     final PropertyEditor editor=property.getEditor();
683     editor.removePropertyEditorListener(myPropertyEditorListener);
684     try {
685       if (myEditor != null && !myEditor.isUndoRedoInProgress()) {
686         final Object value = editor.getValue();
687         setValueAt(value, editingRow, editingColumn);
688       }
689     }
690     catch (final Exception exc) {
691       showInvalidInput(exc);
692     }
693     finally {
694       removeEditor();
695       myStoppingEditing = false;
696     }
697   }
698
699   private static void showInvalidInput(final Exception exc) {
700     final Throwable cause = exc.getCause();
701     String message;
702     if(cause != null){
703       message = cause.getMessage();
704     }
705     else{
706       message = exc.getMessage();
707     }
708     if (message == null || message.length() == 0) {
709       message = UIDesignerBundle.message("error.no.message");
710     }
711     Messages.showMessageDialog(UIDesignerBundle.message("error.setting.value", message),
712                                UIDesignerBundle.message("title.invalid.input"), Messages.getErrorIcon());
713   }
714
715   /**
716    * Expands property with the specified index. The method fires event that
717    * model changes and keeps currently selected row.
718    */
719   private void expandProperty(final int index){
720     final int selectedRow=getSelectedRow();
721
722     // Expand property
723     final Property property=myProperties.get(index);
724     final String dottedName = getDottedName(property);
725
726     // it's possible that property was expanded and we switched to a component which doesn't have this property
727     if (myExpandedProperties.contains(dottedName)) return;
728     myExpandedProperties.add(dottedName);
729
730     final Property[] children=getPropChildren(property);
731     for (int i = 0; i < children.length; i++) {
732       myProperties.add(index + i + 1, children[i]);
733     }
734     myModel.fireTableDataChanged();
735
736     // Restore selected row
737     if(selectedRow!=-1){
738       getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
739     }
740   }
741
742   /**
743    * Collapse property with the specified index. The method fires event that
744    * model changes and keeps currently selected row.
745    */
746   private void collapseProperty(final int index){
747     final int selectedRow=getSelectedRow();
748
749     // Expand property
750     final Property property=myProperties.get(index);
751     LOG.assertTrue(isPropertyExpanded(property));
752     myExpandedProperties.remove(getDottedName(property));
753
754     final Property[] children=getPropChildren(property);
755     for (int i=0; i<children.length; i++){
756       myProperties.remove(index + 1);
757     }
758     myModel.fireTableDataChanged();
759
760     // Restore selected row
761     if(selectedRow!=-1){
762       getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
763     }
764   }
765
766   @Nullable
767   ErrorInfo getErrorInfoForRow(final int row) {
768     LOG.assertTrue(row < myProperties.size());
769     if (mySelection.size() != 1) {
770       return null;
771     }
772     RadComponent component = mySelection.get(0);
773     final Property property = myProperties.get(row);
774     ErrorInfo errorInfo = null;
775     if(myClassToBindProperty.equals(property)){
776       errorInfo = (ErrorInfo)component.getClientProperty(ErrorAnalyzer.CLIENT_PROP_CLASS_TO_BIND_ERROR);
777     }
778     else if(myBindingProperty.equals(property)){
779       errorInfo = (ErrorInfo)component.getClientProperty(ErrorAnalyzer.CLIENT_PROP_BINDING_ERROR);
780     }
781     else {
782       //noinspection unchecked
783       ArrayList<ErrorInfo> errors = (ArrayList<ErrorInfo>) component.getClientProperty(ErrorAnalyzer.CLIENT_PROP_ERROR_ARRAY);
784       if (errors != null) {
785         for(ErrorInfo err: errors) {
786           if (property.getName().equals(err.getPropertyName())) {
787             errorInfo = err;
788             break;
789           }
790         }
791       }
792     }
793     return errorInfo;
794   }
795
796   /**
797    * @return first error for the property at the specified row. If component doesn't contain
798    * any error then the method returns {@code null}.
799    */
800   @Nullable
801   private String getErrorForRow(final int row){
802     LOG.assertTrue(row < myProperties.size());
803     final ErrorInfo errorInfo = getErrorInfoForRow(row);
804     return errorInfo != null ? errorInfo.myDescription : null;
805   }
806
807   @Override
808   public String getToolTipText(final MouseEvent e) {
809     final int row = rowAtPoint(e.getPoint());
810     if(row == -1){
811       return null;
812     }
813     return getErrorForRow(row);
814   }
815
816   private Object getSelectionValue(final Property property) {
817     if (mySelection.size() == 0) {
818       return null;
819     }
820     //noinspection unchecked
821     Object result = property.getValue(mySelection.get(0));
822     for(int i=1; i<mySelection.size(); i++) {
823       Object otherValue = null;
824       if (property instanceof IntrospectedProperty) {
825         IntrospectedProperty[] props = Palette.getInstance(myProject).getIntrospectedProperties(mySelection.get(i));
826         for(IntrospectedProperty otherProperty: props) {
827           if (otherProperty.getName().equals(property.getName())) {
828             otherValue = otherProperty.getValue(mySelection.get(i));
829             break;
830           }
831         }
832       }
833       else {
834         //noinspection unchecked
835         otherValue = property.getValue(mySelection.get(i));
836       }
837       if (!Comparing.equal(result, otherValue)) {
838         return null;
839       }
840     }
841     return result;
842   }
843
844   /**
845    * @return false if some of the set value operations have failed; true if everything successful
846    */
847   private boolean setSelectionValue(Property property, Object newValue) {
848     if (!setPropValue(property, mySelection.get(0), newValue)) return false;
849     for(int i=1; i<mySelection.size(); i++) {
850       if (property instanceof IntrospectedProperty) {
851         IntrospectedProperty[] props = Palette.getInstance(myProject).getIntrospectedProperties(mySelection.get(i));
852         for(IntrospectedProperty otherProperty: props) {
853           if (otherProperty.getName().equals(property.getName())) {
854             if (!setPropValue(otherProperty, mySelection.get(i), newValue)) return false;
855             break;
856           }
857         }
858       }
859       else {
860         if (!setPropValue(property, mySelection.get(i), newValue)) return false;
861       }
862     }
863     return true;
864   }
865
866   private static boolean setPropValue(final Property property, final RadComponent c, final Object newValue) {
867     try {
868       //noinspection unchecked
869       property.setValue(c, newValue);
870     }
871     catch (Throwable e) {
872       LOG.debug(e);
873       if(e instanceof InvocationTargetException){ // special handling of warapped exceptions
874         e = ((InvocationTargetException)e).getTargetException();
875       }
876       Messages.showMessageDialog(e.getMessage(), UIDesignerBundle.message("title.invalid.input"), Messages.getErrorIcon());
877       return false;
878     }
879     return true;
880   }
881
882   public boolean isModifiedForSelection(final Property property) {
883     for(RadComponent c: mySelection) {
884       //noinspection unchecked
885       if (property.isModified(c)) {
886         return true;
887       }
888     }
889     return false;
890   }
891
892   /**
893    * Adapter to TableModel
894    */
895   private final class MyModel extends AbstractTableModel {
896     private final String[] myColumnNames;
897
898     public MyModel(){
899       myColumnNames=new String[]{
900         UIDesignerBundle.message("column.property"),
901         UIDesignerBundle.message("column.value")};
902     }
903
904     @Override
905     public int getColumnCount(){
906       return 2;
907     }
908
909     @Override
910     public String getColumnName(final int column){
911       return myColumnNames[column];
912     }
913
914     @Override
915     public int getRowCount(){
916       return myProperties.size();
917     }
918
919     @Override
920     public boolean isCellEditable(final int row, final int column){
921       return  column==1 && myProperties.get(row).getEditor() != null;
922     }
923
924     @Override
925     public Object getValueAt(final int row, final int column){
926       return myProperties.get(row);
927     }
928
929     @Override
930     public void setValueAt(final Object newValue, final int row, final int column){
931       if (column != 1){
932         throw new IllegalArgumentException("wrong index: " + column);
933       }
934       setValueAtRow(row, newValue);
935     }
936
937     boolean setValueAtRow(final int row, final Object newValue) {
938       final Property property=myProperties.get(row);
939
940       // Optimization: do nothing if value doesn't change
941       final Object oldValue=getSelectionValue(property);
942       boolean retVal = true;
943       if(!Comparing.equal(oldValue,newValue)){
944         final GuiEditor editor = myEditor;
945         if (!editor.ensureEditable()) {
946           return false;
947         }
948         final Ref<Boolean> result = new Ref<>(Boolean.FALSE);
949         CommandProcessor.getInstance().executeCommand(myProject, () -> {
950           result.set(setSelectionValue(property, newValue));
951
952           editor.refreshAndSave(false);
953         }, UIDesignerBundle.message("command.set.property.value"), null);
954
955         retVal = result.get().booleanValue();
956       }
957       if (property.needRefreshPropertyList() && retVal) {
958         synchWithTree(true);
959       }
960       return retVal;
961     }
962   }
963
964   private final class MyPropertyEditorListener extends PropertyEditorAdapter{
965     @Override
966     public void valueCommitted(final PropertyEditor source, final boolean continueEditing, final boolean closeEditorOnError){
967       if(isEditing()){
968         final Object value;
969         final TableCellEditor tableCellEditor = cellEditor;
970         try {
971           value = tableCellEditor.getCellEditorValue();
972         }
973         catch (final Exception exc) {
974           showInvalidInput(exc);
975           return;
976         }
977         boolean valueAccepted = myModel.setValueAtRow(editingRow, value);
978         if (valueAccepted) {
979           if (!continueEditing) tableCellEditor.stopCellEditing();
980         }
981         else {
982           if (closeEditorOnError) tableCellEditor.cancelCellEditing();
983         }
984       }
985     }
986
987     @Override
988     public void editingCanceled(final PropertyEditor source) {
989       if(isEditing()){
990         cellEditor.cancelCellEditing();
991       }
992     }
993   }
994
995   private final class MyCompositeTableCellRenderer implements TableCellRenderer{
996     /**
997      * This renderer paints first column with property names
998      */
999     private final ColoredTableCellRenderer myPropertyNameRenderer;
1000     private final ColoredTableCellRenderer myErrorRenderer;
1001     private final Icon myExpandIcon;
1002     private final Icon myCollapseIcon;
1003     private final Icon myIndentedExpandIcon;
1004     private final Icon myIndentedCollapseIcon;
1005     private final Icon[] myIndentIcons = new Icon[3];
1006
1007     public MyCompositeTableCellRenderer(){
1008       myPropertyNameRenderer = new ColoredTableCellRenderer() {
1009         @Override
1010         protected void customizeCellRenderer(
1011           final JTable table,
1012           final Object value,
1013           final boolean selected,
1014           final boolean hasFocus,
1015           final int row,
1016           final int column
1017         ) {
1018           // We will append text later in the
1019           setPaintFocusBorder(false);
1020           setFocusBorderAroundIcon(true);
1021         }
1022       };
1023
1024       myErrorRenderer = new ColoredTableCellRenderer() {
1025         @Override
1026         protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
1027           setPaintFocusBorder(false);
1028         }
1029       };
1030
1031       myExpandIcon = UIUtil.isUnderDarcula() ? AllIcons.Mac.Tree_white_right_arrow : UIDesignerIcons.ExpandNode;
1032       myCollapseIcon = UIUtil.isUnderDarcula() ? AllIcons.Mac.Tree_white_down_arrow : UIDesignerIcons.CollapseNode;
1033       for (int i = 0; i < myIndentIcons.length; i++) {
1034         myIndentIcons[i] = EmptyIcon.create(myExpandIcon.getIconWidth() + getPropertyIndentWidth() * i, myExpandIcon.getIconHeight());
1035       }
1036       myIndentedExpandIcon = new IndentedIcon(myExpandIcon, getPropertyIndentWidth());
1037       myIndentedCollapseIcon = new IndentedIcon(myCollapseIcon, getPropertyIndentWidth());
1038     }
1039
1040     @Override
1041     public Component getTableCellRendererComponent(
1042       final JTable table,
1043       @NotNull final Object value,
1044       final boolean selected,
1045       final boolean hasFocus,
1046       final int row,
1047       int column
1048     ){
1049       myPropertyNameRenderer.getTableCellRendererComponent(table,value,selected,hasFocus,row,column);
1050
1051       column=table.convertColumnIndexToModel(column);
1052       final Property property=(Property)value;
1053
1054       final Color background;
1055       final Property parent = property.getParent();
1056       if (property instanceof IntrospectedProperty){
1057         background = table.getBackground();
1058       }
1059       else {
1060         // syntetic property
1061         background = parent == null ? SYNTETIC_PROPERTY_BACKGROUND : SYNTETIC_SUBPROPERTY_BACKGROUND;
1062       }
1063
1064       if (!selected){
1065         myPropertyNameRenderer.setBackground(background);
1066       }
1067
1068       if(column==0){ // painter for first column
1069         SimpleTextAttributes attrs = getTextAttributes(row, property);
1070         myPropertyNameRenderer.append(property.getName(), attrs);
1071
1072         // 2. Icon
1073         if(getPropChildren(property).length>0) {
1074           // This is composite property and we have to show +/- sign
1075           if (parent != null) {
1076             if(isPropertyExpanded(property)){
1077               myPropertyNameRenderer.setIcon(myIndentedCollapseIcon);
1078             }else{
1079               myPropertyNameRenderer.setIcon(myIndentedExpandIcon);
1080             }
1081           }
1082           else {
1083             if(isPropertyExpanded(property)){
1084               myPropertyNameRenderer.setIcon(myCollapseIcon);
1085             }else{
1086               myPropertyNameRenderer.setIcon(myExpandIcon);
1087             }
1088           }
1089         }else{
1090           // If property doesn't have children then we have shift its text
1091           // to the right
1092           myPropertyNameRenderer.setIcon(myIndentIcons [getPropertyIndentDepth(property)]);
1093         }
1094       }
1095       else if(column==1){ // painter for second column
1096         try {
1097           final PropertyRenderer renderer=property.getRenderer();
1098           //noinspection unchecked
1099           final JComponent component = renderer.getComponent(myEditor.getRootContainer(), getSelectionValue(property),
1100                                                              selected, hasFocus);
1101           if (!selected) {
1102             component.setBackground(background);
1103           }
1104           if (isModifiedForSelection(property)) {
1105             component.setFont(table.getFont().deriveFont(Font.BOLD));
1106           }
1107           else {
1108             component.setFont(table.getFont());
1109           }
1110
1111           if (component instanceof JCheckBox) {
1112             component.putClientProperty( "JComponent.sizeVariant", UIUtil.isUnderAquaLookAndFeel() ? "small" : null);
1113           }
1114
1115           return component;
1116         }
1117         catch(Exception ex) {
1118           LOG.debug(ex);
1119           myErrorRenderer.clear();
1120           myErrorRenderer.append(UIDesignerBundle.message("error.getting.value", ex.getMessage()), SimpleTextAttributes.ERROR_ATTRIBUTES);
1121           return myErrorRenderer;
1122         }
1123       }
1124       else{
1125         throw new IllegalArgumentException("wrong column: "+column);
1126       }
1127
1128       if (!selected) {
1129         myPropertyNameRenderer.setForeground(PropertyInspectorTable.this.getForeground());
1130         if(property instanceof IntrospectedProperty){
1131           final RadComponent component = mySelection.get(0);
1132           final Class componentClass = component.getComponentClass();
1133           if (Properties.getInstance().isExpertProperty(component.getModule(), componentClass, property.getName())) {
1134             myPropertyNameRenderer.setForeground(Color.LIGHT_GRAY);
1135           }
1136         }
1137       }
1138
1139       return myPropertyNameRenderer;
1140     }
1141
1142     private SimpleTextAttributes getTextAttributes(final int row, final Property property) {
1143       // 1. Text
1144       ErrorInfo errInfo = getErrorInfoForRow(row);
1145
1146       SimpleTextAttributes result;
1147       boolean modified;
1148       try {
1149         modified = isModifiedForSelection(property);
1150       }
1151       catch(Exception ex) {
1152         // ignore exceptions here - they'll be reported as red property values
1153         modified = false;
1154       }
1155       if (errInfo == null) {
1156         result = modified ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES;
1157       }
1158       else {
1159         final HighlightSeverity severity = errInfo.getHighlightDisplayLevel().getSeverity();
1160         Map<HighlightSeverity, SimpleTextAttributes> cache = modified ? myModifiedHighlightAttributes : myHighlightAttributes;
1161         result = cache.get(severity);
1162         if (result == null) {
1163           final TextAttributesKey attrKey = SeverityRegistrar.getSeverityRegistrar(myProject).getHighlightInfoTypeBySeverity(severity).getAttributesKey();
1164           TextAttributes textAttrs = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(attrKey);
1165           if (modified) {
1166             textAttrs = textAttrs.clone();
1167             textAttrs.setFontType(textAttrs.getFontType() | Font.BOLD);
1168           }
1169           result = SimpleTextAttributes.fromTextAttributes(textAttrs);
1170           cache.put(severity, result);
1171         }
1172       }
1173
1174       if (property instanceof IntrospectedProperty) {
1175         final RadComponent c = mySelection.get(0);
1176         if (Properties.getInstance().isPropertyDeprecated(c.getModule(), c.getComponentClass(), property.getName())) {
1177           return new SimpleTextAttributes(result.getBgColor(), result.getFgColor(), result.getWaveColor(),
1178                                           result.getStyle() | SimpleTextAttributes.STYLE_STRIKEOUT);
1179         }
1180       }
1181
1182       return result;
1183     }
1184   }
1185
1186   /**
1187    * This is adapter from PropertyEditor to TableCellEditor interface
1188    */
1189   private final class MyCellEditor extends AbstractCellEditor implements TableCellEditor{
1190     private PropertyEditor myEditor;
1191
1192     public void setEditor(@NotNull final PropertyEditor editor){
1193       myEditor = editor;
1194     }
1195
1196     @Override
1197     public Object getCellEditorValue(){
1198       try {
1199         return myEditor.getValue();
1200       }
1201       catch (Exception e) {
1202         throw new RuntimeException(e);
1203       }
1204     }
1205
1206     @Override
1207     public Component getTableCellEditorComponent(final JTable table, @NotNull final Object value, final boolean isSelected, final int row, final int column){
1208       final Property property=(Property)value;
1209       try {
1210         //noinspection unchecked
1211         final JComponent c = myEditor.getComponent(mySelection.get(0), getSelectionValue(property), null);
1212         if (c instanceof JComboBox) {
1213           c.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
1214         } else if (c instanceof JCheckBox) {
1215           c.putClientProperty( "JComponent.sizeVariant", UIUtil.isUnderAquaLookAndFeel() ? "small" : null);
1216         }
1217
1218         return c;
1219       }
1220       catch(Exception ex) {
1221         LOG.debug(ex);
1222         SimpleColoredComponent errComponent = new SimpleColoredComponent();
1223         errComponent.append(UIDesignerBundle.message("error.getting.value", ex.getMessage()), SimpleTextAttributes.ERROR_ATTRIBUTES);
1224         return errComponent;
1225       }
1226     }
1227   }
1228
1229   /**
1230    * Reimplementation of LookAndFeel's SelectPreviousRowAction action.
1231    * Standard implementation isn't smart enough.
1232    *
1233    * @see javax.swing.plaf.basic.BasicTableUI
1234    */
1235   private final class MySelectPreviousRowAction extends AbstractAction{
1236     @Override
1237     public void actionPerformed(final ActionEvent e){
1238       final int rowCount=getRowCount();
1239       LOG.assertTrue(rowCount>0);
1240       int selectedRow=getSelectedRow();
1241       if(selectedRow!=-1){
1242         selectedRow -= 1;
1243       }
1244       selectedRow=(selectedRow+rowCount)%rowCount;
1245       if(isEditing()){
1246         finishEditing();
1247         getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
1248         scrollRectToVisible(getCellRect(selectedRow, 0, true));
1249         startEditing(selectedRow);
1250       } else {
1251         getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
1252         scrollRectToVisible(getCellRect(selectedRow, 0, true));
1253       }
1254     }
1255   }
1256
1257   /**
1258    * Reimplementation of LookAndFeel's SelectNextRowAction action.
1259    * Standard implementation isn't smart enough.
1260    *
1261    * @see javax.swing.plaf.basic.BasicTableUI
1262    */
1263   private final class MySelectNextRowAction extends AbstractAction{
1264     @Override
1265     public void actionPerformed(final ActionEvent e){
1266       final int rowCount=getRowCount();
1267       LOG.assertTrue(rowCount>0);
1268       final int selectedRow=(getSelectedRow()+1)%rowCount;
1269       if(isEditing()){
1270         finishEditing();
1271         getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
1272         scrollRectToVisible(getCellRect(selectedRow, 0, true));
1273         startEditing(selectedRow);
1274       }else{
1275         getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
1276         scrollRectToVisible(getCellRect(selectedRow, 0, true));
1277       }
1278     }
1279   }
1280
1281   /**
1282    * Reimplementation of LookAndFeel's StartEditingAction action.
1283    * Standard implementation isn't smart enough.
1284    *
1285    * @see javax.swing.plaf.basic.BasicTableUI
1286    */
1287   private final class MyStartEditingAction extends AbstractAction{
1288     @Override
1289     public void actionPerformed(final ActionEvent e){
1290       final int selectedRow=getSelectedRow();
1291       if(selectedRow==-1 || isEditing()){
1292         return;
1293       }
1294
1295       startEditing(selectedRow);
1296     }
1297   }
1298
1299   /**
1300    * Expands property which has children or start editing atomic
1301    * property.
1302    */
1303   private final class MyEnterAction extends AbstractAction{
1304     @Override
1305     public void actionPerformed(final ActionEvent e){
1306       final int selectedRow=getSelectedRow();
1307       if(isEditing() || selectedRow==-1){
1308         return;
1309       }
1310
1311       final Property property=myProperties.get(selectedRow);
1312       if(getPropChildren(property).length>0){
1313         if(isPropertyExpanded(property)){
1314           collapseProperty(selectedRow);
1315         }else{
1316           expandProperty(selectedRow);
1317         }
1318       }else{
1319         startEditing(selectedRow);
1320       }
1321     }
1322   }
1323
1324   private class MyExpandCurrentAction extends AbstractAction {
1325     private final boolean myExpand;
1326
1327     public MyExpandCurrentAction(final boolean expand) {
1328       myExpand = expand;
1329     }
1330
1331     @Override
1332     public void actionPerformed(ActionEvent e) {
1333       final int selectedRow=getSelectedRow();
1334       if(isEditing() || selectedRow==-1){
1335         return;
1336       }
1337       final Property property=myProperties.get(selectedRow);
1338       if(getPropChildren(property).length>0) {
1339         if (myExpand) {
1340           if (!isPropertyExpanded(property)) {
1341             expandProperty(selectedRow);
1342           }
1343         }
1344         else {
1345           if (isPropertyExpanded(property)) {
1346             collapseProperty(selectedRow);
1347           }
1348         }
1349       }
1350     }
1351   }
1352
1353   /**
1354    * Updates UI of editors and renderers of all introspected properties
1355    */
1356   private final class MyLafManagerListener implements LafManagerListener{
1357     /**
1358      * Recursively updates renderer and editor UIs of all synthetic
1359      * properties.
1360      */
1361     private void updateUI(final Property property){
1362       final PropertyRenderer renderer = property.getRenderer();
1363       renderer.updateUI();
1364       final PropertyEditor editor = property.getEditor();
1365       if(editor != null){
1366         editor.updateUI();
1367       }
1368       final Property[] children = property.getChildren(null);
1369       for (int i = children.length - 1; i >= 0; i--) {
1370         final Property child = children[i];
1371         if(!(child instanceof IntrospectedProperty)){
1372           updateUI(child);
1373         }
1374       }
1375     }
1376
1377     @Override
1378     public void lookAndFeelChanged(final LafManager source) {
1379       updateUI(myBorderProperty);
1380       updateUI(MarginProperty.getInstance(myProject));
1381       updateUI(HGapProperty.getInstance(myProject));
1382       updateUI(VGapProperty.getInstance(myProject));
1383       updateUI(HSizePolicyProperty.getInstance(myProject));
1384       updateUI(VSizePolicyProperty.getInstance(myProject));
1385       updateUI(HorzAlignProperty.getInstance(myProject));
1386       updateUI(VertAlignProperty.getInstance(myProject));
1387       updateUI(IndentProperty.getInstance(myProject));
1388       updateUI(UseParentLayoutProperty.getInstance(myProject));
1389       updateUI(MinimumSizeProperty.getInstance(myProject));
1390       updateUI(PreferredSizeProperty.getInstance(myProject));
1391       updateUI(MaximumSizeProperty.getInstance(myProject));
1392       updateUI(myButtonGroupProperty);
1393       updateUI(myLayoutManagerProperty);
1394       updateUI(SameSizeHorizontallyProperty.getInstance(myProject));
1395       updateUI(SameSizeVerticallyProperty.getInstance(myProject));
1396       updateUI(CustomCreateProperty.getInstance(myProject));
1397       updateUI(ClientPropertiesProperty.getInstance(myProject));
1398     }
1399   }
1400
1401
1402 }