replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / palette / Palette.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.palette;
17
18 import com.intellij.ide.ui.LafManager;
19 import com.intellij.ide.ui.LafManagerListener;
20 import com.intellij.openapi.Disposable;
21 import com.intellij.openapi.application.ApplicationNamesInfo;
22 import com.intellij.openapi.components.PersistentStateComponent;
23 import com.intellij.openapi.components.ServiceManager;
24 import com.intellij.openapi.components.State;
25 import com.intellij.openapi.components.Storage;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.Messages;
29 import com.intellij.openapi.util.Condition;
30 import com.intellij.uiDesigner.Properties;
31 import com.intellij.uiDesigner.SwingProperties;
32 import com.intellij.uiDesigner.UIDesignerBundle;
33 import com.intellij.uiDesigner.core.GridConstraints;
34 import com.intellij.uiDesigner.lw.LwXmlReader;
35 import com.intellij.uiDesigner.lw.StringDescriptor;
36 import com.intellij.uiDesigner.propertyInspector.IntrospectedProperty;
37 import com.intellij.uiDesigner.propertyInspector.Property;
38 import com.intellij.uiDesigner.propertyInspector.PropertyEditor;
39 import com.intellij.uiDesigner.propertyInspector.PropertyRenderer;
40 import com.intellij.uiDesigner.propertyInspector.editors.IntEnumEditor;
41 import com.intellij.uiDesigner.propertyInspector.properties.*;
42 import com.intellij.uiDesigner.propertyInspector.renderers.IntEnumRenderer;
43 import com.intellij.uiDesigner.radComponents.RadComponent;
44 import com.intellij.util.containers.ContainerUtil;
45 import org.jdom.Document;
46 import org.jdom.Element;
47 import org.jdom.input.SAXBuilder;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51
52 import javax.swing.*;
53 import java.awt.*;
54 import java.beans.BeanInfo;
55 import java.beans.IntrospectionException;
56 import java.beans.Introspector;
57 import java.beans.PropertyDescriptor;
58 import java.lang.reflect.Method;
59 import java.util.ArrayList;
60 import java.util.HashMap;
61 import java.util.List;
62 import java.util.Map;
63
64 /**
65  * @author Anton Katilin
66  * @author Vladimir Kondratyev
67  */
68 @State(name = "Palette2", defaultStateAsResource = true, storages = @Storage("uiDesigner.xml"))
69 public final class Palette implements Disposable, PersistentStateComponent<Element> {
70   private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.palette.Palette");
71
72   private final MyLafManagerListener myLafManagerListener;
73   private final Map<Class, IntrospectedProperty[]> myClass2Properties;
74   private final Map<String, ComponentItem> myClassName2Item;
75   /*All groups in the palette*/
76   private final List<GroupItem> myGroups;
77   /*Listeners, etc*/
78   private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
79   private final Project myProject;
80   private final GroupItem mySpecialGroup = new GroupItem(true);
81
82   /**
83    * Predefined item for javax.swing.JPanel
84    */
85   private ComponentItem myPanelItem;
86   @NonNls private static final String ATTRIBUTE_VSIZE_POLICY = "vsize-policy";
87   @NonNls private static final String ATTRIBUTE_HSIZE_POLICY = "hsize-policy";
88   @NonNls private static final String ATTRIBUTE_ANCHOR = "anchor";
89   @NonNls private static final String ATTRIBUTE_FILL = "fill";
90   @NonNls private static final String ELEMENT_MINIMUM_SIZE = "minimum-size";
91   @NonNls private static final String ATTRIBUTE_WIDTH = "width";
92   @NonNls private static final String ATTRIBUTE_HEIGHT = "height";
93   @NonNls private static final String ELEMENT_PREFERRED_SIZE = "preferred-size";
94   @NonNls private static final String ELEMENT_MAXIMUM_SIZE = "maximum-size";
95   @NonNls private static final String ATTRIBUTE_CLASS = "class";
96   @NonNls private static final String ATTRIBUTE_ICON = "icon";
97   @NonNls private static final String ATTRIBUTE_TOOLTIP_TEXT = "tooltip-text";
98   @NonNls private static final String ELEMENT_DEFAULT_CONSTRAINTS = "default-constraints";
99   @NonNls private static final String ELEMENT_INITIAL_VALUES = "initial-values";
100   @NonNls private static final String ELEMENT_PROPERTY = "property";
101   @NonNls private static final String ATTRIBUTE_NAME = "name";
102   @NonNls private static final String ATTRIBUTE_VALUE = "value";
103   @NonNls private static final String ATTRIBUTE_REMOVABLE = "removable";
104   @NonNls private static final String ELEMENT_ITEM = "item";
105   @NonNls private static final String ELEMENT_GROUP = "group";
106   @NonNls private static final String ATTRIBUTE_VERSION = "version";
107   @NonNls private static final String ATTRIBUTE_SINCE_VERSION = "since-version";
108   @NonNls private static final String ATTRIBUTE_AUTO_CREATE_BINDING = "auto-create-binding";
109   @NonNls private static final String ATTRIBUTE_CAN_ATTACH_LABEL = "can-attach-label";
110   @NonNls private static final String ATTRIBUTE_IS_CONTAINER = "is-container";
111
112   public static Palette getInstance(@NotNull final Project project) {
113     return ServiceManager.getService(project, Palette.class);
114   }
115
116   /**
117    * Invoked by reflection
118    */
119   public Palette(Project project) {
120     myProject = project;
121     myLafManagerListener = project == null ? null : new MyLafManagerListener();
122     myClass2Properties = new HashMap<>();
123     myClassName2Item = new HashMap<>();
124     myGroups = new ArrayList<>();
125
126     if (project != null) {
127       mySpecialGroup.setReadOnly(true);
128       mySpecialGroup.addItem(ComponentItem.createAnyComponentItem(project));
129     }
130
131     if (myLafManagerListener != null) {
132       LafManager.getInstance().addLafManagerListener(myLafManagerListener);
133     }
134   }
135
136   @Override
137   public Element getState() {
138     Element state = new Element("state");
139     writeGroups(state);
140     return state;
141   }
142
143   @Override
144   public void loadState(Element state) {
145     myClass2Properties.clear();
146     myClassName2Item.clear();
147     myGroups.clear();
148
149     processGroups(state.getChildren(ELEMENT_GROUP));
150
151     // Ensure that all predefined items are loaded
152     LOG.assertTrue(myPanelItem != null);
153
154     if (!state.getAttributeValue(ATTRIBUTE_VERSION, "1").equals("2")) {
155       upgradePalette();
156     }
157   }
158
159   /**
160    * Adds specified listener.
161    */
162   public void addListener(@NotNull final Listener l) {
163     LOG.assertTrue(!myListeners.contains(l));
164     myListeners.add(l);
165   }
166
167   /**
168    * Removes specified listener.
169    */
170   public void removeListener(@NotNull final Listener l) {
171     LOG.assertTrue(myListeners.contains(l));
172     myListeners.remove(l);
173   }
174
175   void fireGroupsChanged() {
176     for (Listener listener : myListeners) {
177       listener.groupsChanged(this);
178     }
179   }
180
181   @Override
182   public void dispose() {
183     if (myLafManagerListener != null) {
184       LafManager.getInstance().removeLafManagerListener(myLafManagerListener);
185     }
186   }
187
188   private void upgradePalette() {
189     // load new components from the predefined Palette2.xml
190     try {
191       //noinspection HardCodedStringLiteral
192       Document document = new SAXBuilder().build(getClass().getResourceAsStream("/idea/Palette2.xml"));
193       for (Element groupElement : document.getRootElement().getChildren(ELEMENT_GROUP)) {
194         for (GroupItem group : myGroups) {
195           if (group.getName().equals(groupElement.getAttributeValue(ATTRIBUTE_NAME))) {
196             upgradeGroup(group, groupElement);
197             break;
198           }
199         }
200       }
201     }
202     catch (Exception e) {
203       LOG.error(e);
204     }
205   }
206
207   private void upgradeGroup(final GroupItem group, final Element groupElement) {
208     for (Element itemElement : groupElement.getChildren(ELEMENT_ITEM)) {
209       if (itemElement.getAttributeValue(ATTRIBUTE_SINCE_VERSION, "").equals("2")) {
210         processItemElement(itemElement, group, true);
211       }
212       final String className = LwXmlReader.getRequiredString(itemElement, ATTRIBUTE_CLASS);
213       final ComponentItem item = getItem(className);
214       if (item != null) {
215         if (LwXmlReader.getOptionalBoolean(itemElement, ATTRIBUTE_AUTO_CREATE_BINDING, false)) {
216           item.setAutoCreateBinding(true);
217         }
218         if (LwXmlReader.getOptionalBoolean(itemElement, ATTRIBUTE_CAN_ATTACH_LABEL, false)) {
219           item.setCanAttachLabel(true);
220         }
221       }
222     }
223   }
224
225   /**
226    * @return a predefined palette item which corresponds to the JPanel.
227    */
228   @NotNull
229   public ComponentItem getPanelItem() {
230     return myPanelItem;
231   }
232
233   /**
234    * @return {@code ComponentItem} for the UI bean with the specified {@code componentClassName}.
235    * The method returns {@code null} if palette has no information about the specified
236    * class.
237    */
238   @Nullable
239   public ComponentItem getItem(@NotNull final String componentClassName) {
240     return myClassName2Item.get(componentClassName);
241   }
242
243   /**
244    * @return read-only list of all groups in the palette.
245    * <em>DO NOT MODIFY OR CACHE THIS LIST</em>.
246    */
247   public List<GroupItem> getGroups() {
248     return myGroups;
249   }
250
251   public GroupItem[] getToolWindowGroups() {
252     GroupItem[] groups = new GroupItem[myGroups.size() + 1];
253     for (int i = 0; i < myGroups.size(); i++) {
254       groups[i] = myGroups.get(i);
255     }
256     groups[myGroups.size()] = mySpecialGroup;
257     return groups;
258   }
259
260   /**
261    * @param groups list of new groups.
262    */
263   public void setGroups(@NotNull final ArrayList<GroupItem> groups) {
264     myGroups.clear();
265     myGroups.addAll(groups);
266
267     fireGroupsChanged();
268   }
269
270   /**
271    * Adds specified {@code item} to the palette.
272    *
273    * @param item item to be added
274    * @throws IllegalArgumentException if an item for the same class
275    *                                            is already exists in the palette
276    */
277   public void addItem(@NotNull final GroupItem group, @NotNull final ComponentItem item) {
278     // class -> item
279     final String componentClassName = item.getClassName();
280     if (getItem(componentClassName) != null) {
281       Messages.showMessageDialog(
282         UIDesignerBundle.message("error.item.already.added", componentClassName),
283         ApplicationNamesInfo.getInstance().getFullProductName(),
284         Messages.getErrorIcon()
285       );
286       return;
287     }
288     myClassName2Item.put(componentClassName, item);
289
290     // group -> items
291     group.addItem(item);
292
293     // Process special predefined item for JPanel
294     if ("javax.swing.JPanel".equals(item.getClassName())) {
295       myPanelItem = item;
296     }
297   }
298
299   public void replaceItem(GroupItem group, ComponentItem oldItem, ComponentItem newItem) {
300     group.replaceItem(oldItem, newItem);
301     myClassName2Item.put(oldItem.getClassName(), newItem);
302   }
303
304   public void removeItem(final GroupItem group, final ComponentItem selectedItem) {
305     group.removeItem(selectedItem);
306     myClassName2Item.remove(selectedItem.getClassName());
307   }
308
309   public GroupItem findGroup(final ComponentItem componentItem) {
310     for (GroupItem group : myGroups) {
311       if (group.contains(componentItem)) {
312         return group;
313       }
314     }
315     return null;
316   }
317
318   /**
319    * Helper method.
320    */
321   private static GridConstraints processDefaultConstraintsElement(@NotNull final Element element) {
322     final GridConstraints constraints = new GridConstraints();
323
324     // grid related attributes
325     constraints.setVSizePolicy(LwXmlReader.getRequiredInt(element, ATTRIBUTE_VSIZE_POLICY));
326     constraints.setHSizePolicy(LwXmlReader.getRequiredInt(element, ATTRIBUTE_HSIZE_POLICY));
327     constraints.setAnchor(LwXmlReader.getRequiredInt(element, ATTRIBUTE_ANCHOR));
328     constraints.setFill(LwXmlReader.getRequiredInt(element, ATTRIBUTE_FILL));
329
330     // minimum size
331     final Element minSizeElement = element.getChild(ELEMENT_MINIMUM_SIZE);
332     if (minSizeElement != null) {
333       constraints.myMinimumSize.width = LwXmlReader.getRequiredInt(minSizeElement, ATTRIBUTE_WIDTH);
334       constraints.myMinimumSize.height = LwXmlReader.getRequiredInt(minSizeElement, ATTRIBUTE_HEIGHT);
335     }
336
337     // preferred size
338     final Element prefSizeElement = element.getChild(ELEMENT_PREFERRED_SIZE);
339     if (prefSizeElement != null) {
340       constraints.myPreferredSize.width = LwXmlReader.getRequiredInt(prefSizeElement, ATTRIBUTE_WIDTH);
341       constraints.myPreferredSize.height = LwXmlReader.getRequiredInt(prefSizeElement, ATTRIBUTE_HEIGHT);
342     }
343
344     // maximum size
345     final Element maxSizeElement = element.getChild(ELEMENT_MAXIMUM_SIZE);
346     if (maxSizeElement != null) {
347       constraints.myMaximumSize.width = LwXmlReader.getRequiredInt(maxSizeElement, ATTRIBUTE_WIDTH);
348       constraints.myMaximumSize.height = LwXmlReader.getRequiredInt(maxSizeElement, ATTRIBUTE_HEIGHT);
349     }
350
351     return constraints;
352   }
353
354   private void processItemElement(@NotNull final Element itemElement, @NotNull final GroupItem group, final boolean skipExisting) {
355     // Class name. It's OK if class does not exist.
356     final String className = LwXmlReader.getRequiredString(itemElement, ATTRIBUTE_CLASS);
357     if (skipExisting && getItem(className) != null) {
358       return;
359     }
360
361     // Icon (optional)
362     final String iconPath = LwXmlReader.getString(itemElement, ATTRIBUTE_ICON);
363
364     // Tooltip text (optional)
365     final String toolTipText = LwXmlReader.getString(itemElement, ATTRIBUTE_TOOLTIP_TEXT); // can be null
366
367     boolean autoCreateBinding = LwXmlReader.getOptionalBoolean(itemElement, ATTRIBUTE_AUTO_CREATE_BINDING, false);
368     boolean canAttachLabel = LwXmlReader.getOptionalBoolean(itemElement, ATTRIBUTE_CAN_ATTACH_LABEL, false);
369     boolean isContainer = LwXmlReader.getOptionalBoolean(itemElement, ATTRIBUTE_IS_CONTAINER, false);
370
371     // Default constraint
372     final GridConstraints constraints;
373     final Element defaultConstraints = itemElement.getChild(ELEMENT_DEFAULT_CONSTRAINTS);
374     if (defaultConstraints != null) {
375       constraints = processDefaultConstraintsElement(defaultConstraints);
376     }
377     else {
378       constraints = new GridConstraints();
379     }
380
381     final HashMap<String, StringDescriptor> propertyName2initialValue = new HashMap<>();
382     {
383       final Element initialValues = itemElement.getChild(ELEMENT_INITIAL_VALUES);
384       if (initialValues != null) {
385         for (final Object o : initialValues.getChildren(ELEMENT_PROPERTY)) {
386           final Element e = (Element)o;
387           final String name = LwXmlReader.getRequiredString(e, ATTRIBUTE_NAME);
388           // TODO[all] currently all initial values are strings
389           final StringDescriptor value = StringDescriptor.create(LwXmlReader.getRequiredString(e, ATTRIBUTE_VALUE));
390           propertyName2initialValue.put(name, value);
391         }
392       }
393     }
394
395     final boolean removable = LwXmlReader.getOptionalBoolean(itemElement, ATTRIBUTE_REMOVABLE, true);
396
397     final ComponentItem item = new ComponentItem(
398       myProject,
399       className,
400       iconPath,
401       toolTipText,
402       constraints,
403       propertyName2initialValue,
404       removable,
405       autoCreateBinding,
406       canAttachLabel
407     );
408     item.setIsContainer(isContainer);
409     addItem(group, item);
410   }
411
412   /**
413    * Reads PaletteElements from
414    */
415   private void processGroups(@NotNull List<Element> groupElements) {
416     for (Element groupElement : groupElements) {
417       GroupItem group = new GroupItem(LwXmlReader.getRequiredString(groupElement, ATTRIBUTE_NAME));
418       myGroups.add(group);
419       for (Element itemElement : groupElement.getChildren(ELEMENT_ITEM)) {
420         try {
421           processItemElement(itemElement, group, false);
422         }
423         catch (Exception ex) {
424           LOG.error(ex);
425         }
426       }
427     }
428   }
429
430   /**
431    * Helper method
432    */
433   private static void writeDefaultConstraintsElement(@NotNull final Element itemElement, @NotNull final GridConstraints c) {
434     LOG.assertTrue(ELEMENT_ITEM.equals(itemElement.getName()));
435
436     final Element element = new Element(ELEMENT_DEFAULT_CONSTRAINTS);
437     itemElement.addContent(element);
438
439     // grid related attributes
440     {
441       element.setAttribute(ATTRIBUTE_VSIZE_POLICY, Integer.toString(c.getVSizePolicy()));
442       element.setAttribute(ATTRIBUTE_HSIZE_POLICY, Integer.toString(c.getHSizePolicy()));
443       element.setAttribute(ATTRIBUTE_ANCHOR, Integer.toString(c.getAnchor()));
444       element.setAttribute(ATTRIBUTE_FILL, Integer.toString(c.getFill()));
445     }
446
447     // minimum size
448     {
449       if (c.myMinimumSize.width != -1 || c.myMinimumSize.height != -1) {
450         final Element _element = new Element(ELEMENT_MINIMUM_SIZE);
451         element.addContent(_element);
452         _element.setAttribute(ATTRIBUTE_WIDTH, Integer.toString(c.myMinimumSize.width));
453         _element.setAttribute(ATTRIBUTE_HEIGHT, Integer.toString(c.myMinimumSize.height));
454       }
455     }
456
457     // preferred size
458     {
459       if (c.myPreferredSize.width != -1 || c.myPreferredSize.height != -1) {
460         final Element _element = new Element(ELEMENT_PREFERRED_SIZE);
461         element.addContent(_element);
462         _element.setAttribute(ATTRIBUTE_WIDTH, Integer.toString(c.myPreferredSize.width));
463         _element.setAttribute(ATTRIBUTE_HEIGHT, Integer.toString(c.myPreferredSize.height));
464       }
465     }
466
467     // maximum size
468     {
469       if (c.myMaximumSize.width != -1 || c.myMaximumSize.height != -1) {
470         final Element _element = new Element(ELEMENT_MAXIMUM_SIZE);
471         element.addContent(_element);
472         _element.setAttribute(ATTRIBUTE_WIDTH, Integer.toString(c.myMaximumSize.width));
473         _element.setAttribute(ATTRIBUTE_HEIGHT, Integer.toString(c.myMaximumSize.height));
474       }
475     }
476   }
477
478   /**
479    * Helper method
480    */
481   private static void writeInitialValuesElement(
482     @NotNull final Element itemElement,
483     @NotNull final HashMap<String, StringDescriptor> name2value
484   ) {
485     LOG.assertTrue(ELEMENT_ITEM.equals(itemElement.getName()));
486
487     if (name2value.size() == 0) { // do not append 'initial-values' subtag
488       return;
489     }
490
491     final Element initialValuesElement = new Element(ELEMENT_INITIAL_VALUES);
492     itemElement.addContent(initialValuesElement);
493
494     for (final Map.Entry<String, StringDescriptor> entry : name2value.entrySet()) {
495       final Element propertyElement = new Element(ELEMENT_PROPERTY);
496       initialValuesElement.addContent(propertyElement);
497       propertyElement.setAttribute(ATTRIBUTE_NAME, entry.getKey());
498       propertyElement.setAttribute(ATTRIBUTE_VALUE, entry.getValue().getValue()/*descriptor is always trivial*/);
499     }
500   }
501
502   /**
503    * Helper method
504    */
505   private static void writeComponentItem(@NotNull final Element groupElement, @NotNull final ComponentItem item) {
506     LOG.assertTrue(ELEMENT_GROUP.equals(groupElement.getName()));
507
508     final Element itemElement = new Element(ELEMENT_ITEM);
509     groupElement.addContent(itemElement);
510
511     // Class
512     itemElement.setAttribute(ATTRIBUTE_CLASS, item.getClassName());
513
514     // Tooltip text (if any)
515     if (item.myToolTipText != null) {
516       itemElement.setAttribute(ATTRIBUTE_TOOLTIP_TEXT, item.myToolTipText);
517     }
518
519     // Icon (if any)
520     final String iconPath = item.getIconPath();
521     if (iconPath != null) {
522       itemElement.setAttribute(ATTRIBUTE_ICON, iconPath);
523     }
524
525     // Removable
526     itemElement.setAttribute(ATTRIBUTE_REMOVABLE, Boolean.toString(item.isRemovable()));
527     itemElement.setAttribute(ATTRIBUTE_AUTO_CREATE_BINDING, Boolean.toString(item.isAutoCreateBinding()));
528     itemElement.setAttribute(ATTRIBUTE_CAN_ATTACH_LABEL, Boolean.toString(item.isCanAttachLabel()));
529     if (item.isContainer()) {
530       itemElement.setAttribute(ATTRIBUTE_IS_CONTAINER, Boolean.toString(item.isContainer()));
531     }
532
533     // Default constraints
534     writeDefaultConstraintsElement(itemElement, item.getDefaultConstraints());
535
536     // Initial values (if any)
537     writeInitialValuesElement(itemElement, item.getInitialValues());
538   }
539
540   private void writeGroups(@NotNull Element parentElement) {
541     for (GroupItem group : myGroups) {
542       Element groupElement = new Element(ELEMENT_GROUP);
543       parentElement.addContent(groupElement);
544       groupElement.setAttribute(ATTRIBUTE_NAME, group.getName());
545
546       for (ComponentItem aItemList : group.getItems()) {
547         writeComponentItem(groupElement, aItemList);
548       }
549     }
550   }
551
552   /**
553    * Helper method
554    */
555   private static IntroIntProperty createIntEnumProperty(
556     final String name,
557     final Method readMethod,
558     final Method writeMethod,
559     final IntEnumEditor.Pair[] pairs
560   ) {
561     return new IntroIntProperty(
562       name,
563       readMethod,
564       writeMethod,
565       new IntEnumRenderer(pairs),
566       new IntEnumEditor(pairs), false);
567   }
568
569   @NotNull
570   public IntrospectedProperty[] getIntrospectedProperties(@NotNull final RadComponent component) {
571     return getIntrospectedProperties(component.getComponentClass(), component.getDelegee().getClass());
572   }
573
574   /**
575    * @return arrays of all properties that can be introspected from the
576    * specified class. Only properties with getter and setter methods are
577    * returned.
578    */
579   @NotNull
580   public IntrospectedProperty[] getIntrospectedProperties(@NotNull final Class aClass, @NotNull final Class delegeeClass) {
581     // Try the cache first
582     // TODO[vova, anton] update cache after class reloading (its properties could be hanged).
583     if (myClass2Properties.containsKey(aClass)) {
584       return myClass2Properties.get(aClass);
585     }
586
587     final ArrayList<IntrospectedProperty> result = new ArrayList<>();
588     try {
589       final BeanInfo beanInfo = Introspector.getBeanInfo(aClass);
590       final PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
591       for (final PropertyDescriptor descriptor : descriptors) {
592         Method readMethod = descriptor.getReadMethod();
593         Method writeMethod = descriptor.getWriteMethod();
594         Class propertyType = descriptor.getPropertyType();
595         if (writeMethod == null || readMethod == null || propertyType == null) {
596           continue;
597         }
598
599         boolean storeAsClient = false;
600         try {
601           delegeeClass.getMethod(readMethod.getName(), readMethod.getParameterTypes());
602           delegeeClass.getMethod(writeMethod.getName(), writeMethod.getParameterTypes());
603         }
604         catch (NoSuchMethodException e) {
605           storeAsClient = true;
606         }
607
608         @NonNls final String name = descriptor.getName();
609
610         final IntrospectedProperty property;
611
612         final Properties properties = (myProject == null) ? new Properties() : Properties.getInstance();
613         if (int.class.equals(propertyType)) { // int
614           IntEnumEditor.Pair[] enumPairs = properties.getEnumPairs(aClass, name);
615           if (enumPairs != null) {
616             property = createIntEnumProperty(name, readMethod, writeMethod, enumPairs);
617           }
618           else if (JLabel.class.isAssignableFrom(aClass)) { // special handling for javax.swing.JLabel
619             if (JLabel.class.isAssignableFrom(aClass) &&
620                 ("displayedMnemonic".equals(name) || "displayedMnemonicIndex".equals(name))) { // skip JLabel#displayedMnemonic and JLabel#displayedMnemonicIndex
621               continue;
622             }
623             else {
624               property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
625             }
626           }
627           else if (AbstractButton.class.isAssignableFrom(aClass)) {  // special handling AbstractButton subclasses
628             if ("mnemonic".equals(name) || "displayedMnemonicIndex".equals(name)) { // AbstractButton#mnemonic
629               continue;
630             }
631             else {
632               property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
633             }
634           }
635           else if (JTabbedPane.class.isAssignableFrom(aClass)) {
636             if (SwingProperties.SELECTED_INDEX.equals(name)) {
637               continue;
638             }
639             property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
640           }
641           else {
642             property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
643           }
644         }
645         else if (boolean.class.equals(propertyType)) { // boolean
646           property = new IntroBooleanProperty(name, readMethod, writeMethod, storeAsClient);
647         }
648         else if (double.class.equals(propertyType)) {
649           property = new IntroPrimitiveTypeProperty(name, readMethod, writeMethod, storeAsClient, Double.class);
650         }
651         else if (float.class.equals(propertyType)) {
652           property = new IntroPrimitiveTypeProperty(name, readMethod, writeMethod, storeAsClient, Float.class);
653         }
654         else if (long.class.equals(propertyType)) {
655           property = new IntroPrimitiveTypeProperty(name, readMethod, writeMethod, storeAsClient, Long.class);
656         }
657         else if (byte.class.equals(propertyType)) {
658           property = new IntroPrimitiveTypeProperty(name, readMethod, writeMethod, storeAsClient, Byte.class);
659         }
660         else if (short.class.equals(propertyType)) {
661           property = new IntroPrimitiveTypeProperty(name, readMethod, writeMethod, storeAsClient, Short.class);
662         }
663         else if (char.class.equals(propertyType)) { // java.lang.String
664           property = new IntroCharProperty(name, readMethod, writeMethod, storeAsClient);
665         }
666         else if (String.class.equals(propertyType)) { // java.lang.String
667           property = new IntroStringProperty(name, readMethod, writeMethod, myProject, storeAsClient);
668         }
669         else if (Insets.class.equals(propertyType)) { // java.awt.Insets
670           property = new IntroInsetsProperty(name, readMethod, writeMethod, storeAsClient);
671         }
672         else if (Dimension.class.equals(propertyType)) { // java.awt.Dimension
673           property = new IntroDimensionProperty(name, readMethod, writeMethod, storeAsClient);
674         }
675         else if (Rectangle.class.equals(propertyType)) { // java.awt.Rectangle
676           property = new IntroRectangleProperty(name, readMethod, writeMethod, storeAsClient);
677         }
678         else if (Component.class.isAssignableFrom(propertyType)) {
679           if (JSplitPane.class.isAssignableFrom(aClass) && (name.equals("leftComponent") || name.equals("rightComponent") ||
680                                                             name.equals("topComponent") || name.equals("bottomComponent"))) {
681             // these properties are set through layout
682             continue;
683           }
684           if (JTabbedPane.class.isAssignableFrom(aClass) && name.equals(SwingProperties.SELECTED_COMPONENT)) {
685             // can't set selectedComponent because of set property / add child sequence
686             continue;
687           }
688           if (JMenuBar.class.isAssignableFrom(propertyType) || JPopupMenu.class.isAssignableFrom(propertyType)) {
689             // no menu editing yet
690             continue;
691           }
692           Condition<RadComponent> filter = null;
693           if (name.equals(SwingProperties.LABEL_FOR)) {
694             filter = t -> {
695               ComponentItem item = getItem(t.getComponentClassName());
696               return item != null && item.isCanAttachLabel();
697             };
698           }
699           property = new IntroComponentProperty(name, readMethod, writeMethod, propertyType, filter, storeAsClient);
700         }
701         else if (Color.class.equals(propertyType)) {
702           property = new IntroColorProperty(name, readMethod, writeMethod, storeAsClient);
703         }
704         else if (Font.class.equals(propertyType)) {
705           property = new IntroFontProperty(name, readMethod, writeMethod, storeAsClient);
706         }
707         else if (Icon.class.equals(propertyType)) {
708           property = new IntroIconProperty(name, readMethod, writeMethod, storeAsClient);
709         }
710         else if (ListModel.class.isAssignableFrom(propertyType)) {
711           property = new IntroListModelProperty(name, readMethod, writeMethod, storeAsClient);
712         }
713         else if (Enum.class.isAssignableFrom(propertyType)) {
714           property = new IntroEnumProperty(name, readMethod, writeMethod, storeAsClient, propertyType);
715         }
716         else {
717           // other types are not supported (yet?)
718           continue;
719         }
720
721         result.add(property);
722       }
723     }
724     catch (IntrospectionException e) {
725       throw new RuntimeException(e);
726     }
727
728     final IntrospectedProperty[] properties = result.toArray(new IntrospectedProperty[result.size()]);
729     myClass2Properties.put(aClass, properties);
730     return properties;
731   }
732
733   /**
734    * @return introspected property with the given {@code name} of the
735    * specified {@code class}. The method returns {@code null} if there is no
736    * property with the such name.
737    */
738   @Nullable
739   public IntrospectedProperty getIntrospectedProperty(@NotNull final RadComponent component, @NotNull final String name) {
740     final IntrospectedProperty[] properties = getIntrospectedProperties(component);
741     for (final IntrospectedProperty property : properties) {
742       if (name.equals(property.getName())) {
743         return property;
744       }
745     }
746     return null;
747   }
748
749   /**
750    * @return "inplace" property for the component with the specified class.
751    * <b>DO NOT USE THIS METHOD DIRECTLY</b>. Use {@link RadComponent#getInplaceProperty(int, int) }
752    * instead.
753    */
754   @Nullable
755   public IntrospectedProperty getInplaceProperty(@NotNull final RadComponent component) {
756     final String inplaceProperty = Properties.getInstance().getInplaceProperty(component.getComponentClass());
757     final IntrospectedProperty[] properties = getIntrospectedProperties(component);
758     for (int i = properties.length - 1; i >= 0; i--) {
759       final IntrospectedProperty property = properties[i];
760       if (property.getName().equals(inplaceProperty)) {
761         return property;
762       }
763     }
764     return null;
765   }
766
767   public static boolean isRemovable(@NotNull final GroupItem group) {
768     final ComponentItem[] items = group.getItems();
769     for (int i = items.length - 1; i >= 0; i--) {
770       if (!items[i].isRemovable()) {
771         return false;
772       }
773     }
774     return true;
775   }
776
777   /**
778    * Updates UI of editors and renderers of all introspected properties
779    */
780   private final class MyLafManagerListener implements LafManagerListener {
781     private void updateUI(final Property property) {
782       final PropertyRenderer renderer = property.getRenderer();
783       renderer.updateUI();
784       final PropertyEditor editor = property.getEditor();
785       if (editor != null) {
786         editor.updateUI();
787       }
788       final Property[] children = property.getChildren(null);
789       for (int i = children.length - 1; i >= 0; i--) {
790         updateUI(children[i]);
791       }
792     }
793
794     @Override
795     public void lookAndFeelChanged(final LafManager source) {
796       for (final IntrospectedProperty[] properties : myClass2Properties.values()) {
797         LOG.assertTrue(properties != null);
798         for (int j = properties.length - 1; j >= 0; j--) {
799           updateUI(properties[j]);
800         }
801       }
802     }
803   }
804
805   interface Listener {
806     void groupsChanged(Palette palette);
807   }
808 }