replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / palette / ComponentItem.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.palette;
17
18 import com.intellij.ide.dnd.DnDDragStartBean;
19 import com.intellij.ide.palette.PaletteItem;
20 import com.intellij.openapi.actionSystem.ActionGroup;
21 import com.intellij.openapi.actionSystem.ActionManager;
22 import com.intellij.openapi.actionSystem.CommonDataKeys;
23 import com.intellij.openapi.actionSystem.DataKey;
24 import com.intellij.openapi.actionSystem.LangDataKeys;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.module.ResourceFileUtil;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.IconLoader;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.psi.JavaPsiFacade;
31 import com.intellij.psi.PsiFile;
32 import com.intellij.psi.search.GlobalSearchScope;
33 import com.intellij.ui.ColoredListCellRenderer;
34 import com.intellij.ui.SimpleTextAttributes;
35 import com.intellij.uiDesigner.HSpacer;
36 import com.intellij.uiDesigner.UIDesignerBundle;
37 import com.intellij.uiDesigner.VSpacer;
38 import com.intellij.uiDesigner.binding.FormClassIndex;
39 import com.intellij.uiDesigner.core.GridConstraints;
40 import com.intellij.uiDesigner.lw.StringDescriptor;
41 import com.intellij.uiDesigner.propertyInspector.IntrospectedProperty;
42 import com.intellij.uiDesigner.radComponents.RadAtomicComponent;
43 import icons.UIDesignerIcons;
44 import org.jetbrains.annotations.NonNls;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import javax.swing.*;
49 import java.awt.*;
50 import java.io.IOException;
51 import java.util.HashMap;
52 import java.util.List;
53
54 /**
55  * @author Anton Katilin
56  * @author Vladimir Kondratyev
57  */
58 public final class ComponentItem implements Cloneable, PaletteItem {
59   private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.palette.ComponentItem");
60
61   public static final DataKey<ComponentItem> DATA_KEY = DataKey.create(ComponentItem.class.getName());
62
63   @NonNls private String myClassName;
64   private final GridConstraints myDefaultConstraints;
65   /**
66    * Do not use this member directly. Use {@link #getIcon()} instead.
67    */
68   private Icon myIcon;
69   /**
70    * Do not use this member directly. Use {@link #getSmallIcon()} instead.
71    */
72   private Icon mySmallIcon;
73   /**
74    * @see #getIconPath()
75    * @see #setIconPath(java.lang.String)
76    */
77   private String myIconPath;
78   /**
79    * Do not access this field directly. Use {@link #getToolTipText()} instead.
80    */
81   final String myToolTipText;
82   private final HashMap<String, StringDescriptor> myPropertyName2initialValue;
83   /** Whether item is removable or not */
84   private final boolean myRemovable;
85
86   private boolean myAutoCreateBinding;
87   private boolean myCanAttachLabel;
88   private boolean myIsContainer;
89   private boolean myAnyComponent;
90   private Dimension myInitialSize;
91
92   @NotNull private final Project myProject;
93
94   public ComponentItem(
95     @NotNull Project project,
96     @NotNull final String className,
97     @Nullable final String iconPath,
98     @Nullable final String toolTipText,
99     @NotNull final GridConstraints defaultConstraints,
100     @NotNull final HashMap<String, StringDescriptor> propertyName2initialValue,
101     final boolean removable,
102     final boolean autoCreateBinding,
103     final boolean canAttachLabel
104   ){
105     myAutoCreateBinding = autoCreateBinding;
106     myCanAttachLabel = canAttachLabel;
107     myProject = project;
108     setClassName(className);
109     setIconPath(iconPath);
110
111     myToolTipText = toolTipText;
112     myDefaultConstraints = defaultConstraints;
113     myPropertyName2initialValue = propertyName2initialValue;
114
115     myRemovable = removable;
116   }
117
118   /**
119    * @return whether the item is removable from palette or not.
120    */
121   public boolean isRemovable() {
122     return myRemovable;
123   }
124
125   private static String calcToolTipText(@NotNull final String className) {
126     final int lastDotIndex = className.lastIndexOf('.');
127     if (lastDotIndex != -1 && lastDotIndex != className.length() - 1/*not the last char in class name*/) {
128       return className.substring(lastDotIndex + 1) + " (" + className.substring(0, lastDotIndex) + ")";
129     }
130     else{
131       return className;
132     }
133   }
134
135   /** Creates deep copy of the object. You can edit any properties of the returned object. */
136   public ComponentItem clone(){
137     final ComponentItem result = new ComponentItem(
138       myProject,
139       myClassName,
140       myIconPath,
141       myToolTipText,
142       (GridConstraints)myDefaultConstraints.clone(),
143       (HashMap<String, StringDescriptor>)myPropertyName2initialValue.clone(),
144       myRemovable,
145       myAutoCreateBinding,
146       myCanAttachLabel
147     );
148     result.setIsContainer(myIsContainer);
149     return result;
150   }
151
152   /**
153    * @return string that represents path in the JAR file system that was used to load
154    * icon returned by {@link #getIcon()} method. This method can returns {@code null}.
155    * It means that palette item has some "unknown" item.
156    */
157   @Nullable String getIconPath() {
158     return myIconPath;
159   }
160
161   /**
162    * @param iconPath new path inside JAR file system. {@code null} means that
163    * {@code iconPath} is not specified and some "unknown" icon should be used
164    * to represent the {@link ComponentItem} in UI.
165    */
166   void setIconPath(@Nullable final String iconPath){
167     myIcon = null; // reset cached icon
168     mySmallIcon = null; // reset cached icon
169
170     myIconPath = iconPath;
171   }
172
173   /**
174    * @return item's icon. This icon is used to represent item at the toolbar.
175    * Note, that the method never returns {@code null}. It returns some
176    * default "unknown" icon for the items that has no specified icon in the XML.
177    */
178   @NotNull public Icon getIcon() {
179     // Check cached value first
180     if(myIcon != null){
181       return myIcon;
182     }
183
184     // Create new icon
185     if(myIconPath != null && myIconPath.length() > 0) {
186       final VirtualFile iconFile = ResourceFileUtil.findResourceFileInScope(myIconPath, myProject, GlobalSearchScope.allScope(myProject));
187       if (iconFile != null) {
188         try {
189           myIcon = new ImageIcon(iconFile.contentsToByteArray());
190         }
191         catch (IOException e) {
192           myIcon = null;
193         }
194       }
195       else {
196         myIcon = IconLoader.findIcon(myIconPath);
197       }
198     }
199     if(myIcon == null){
200       myIcon = UIDesignerIcons.Unknown;
201      }
202     LOG.assertTrue(myIcon != null);
203     return myIcon;
204   }
205
206   /**
207    * @return small item's icon. This icon represents component in the
208    * component tree. The method never returns {@code null}. It returns some
209    * default "unknown" icon for the items that has no specified icon in the XML.
210    */
211   @NotNull public Icon getSmallIcon() {
212     // Check cached value first
213     if(mySmallIcon != null){
214       return myIcon;
215     }
216
217     // [vova] It's safe to cast to ImageIcon here because all icons loaded by IconLoader
218     // are ImageIcon(s).
219     final Icon icon = getIcon();
220     if (icon instanceof ImageIcon) {
221       final ImageIcon imageIcon = (ImageIcon)icon;
222       mySmallIcon = new MySmallIcon(imageIcon.getImage());
223     }
224     else {
225       mySmallIcon = icon;
226     }
227
228     return mySmallIcon;
229   }
230
231   /**
232    * @return name of component's class which is represented by the item.
233    */
234   @NotNull public String getClassName() {
235     return myClassName;
236   }
237
238   public String getClassShortName() {
239     final int lastDotIndex = myClassName.lastIndexOf('.');
240     if (lastDotIndex != -1 && lastDotIndex != myClassName.length() - 1/*not the last char in class name*/) {
241       return myClassName.substring(lastDotIndex + 1).replace('$', '.');
242     }
243     else{
244       return myClassName.replace('$', '.');
245     }
246   }
247
248   /**
249    * @param className name of the class that will be instanteated when user drop
250    * item on the form. Cannot be {@code null}. If the class does not exist or
251    * could not be instanteated (for example, class has no default constructor,
252    * it's not a subclass of JComponent, etc) then placeholder component will be
253    * added to the form.
254    */
255   public void setClassName(@NotNull final String className){
256     myClassName = className;
257   }
258
259   public String getToolTipText() {
260     return myToolTipText != null ? myToolTipText : calcToolTipText(myClassName);
261   }
262
263   @NotNull public GridConstraints getDefaultConstraints() {
264     return myDefaultConstraints;
265   }
266
267   /**
268    * The method returns initial value of the property. Term
269    * "initial" means that just after creation of RadComponent
270    * all its properties are set into initial values.
271    * The method returns {@code null} if the
272    * initial property is not defined. Unfortunately we cannot
273    * put this method into the constuctor of {@code RadComponent}.
274    * The problem is that {@code RadComponent} is used in the
275    * code genaration and code generation doesn't depend on any
276    * {@code ComponentItem}, so we need to initialize {@code RadComponent}
277    * in all places where it's needed explicitly.
278    */
279   public Object getInitialValue(final IntrospectedProperty property){
280     return myPropertyName2initialValue.get(property.getName());
281   }
282
283   /**
284    * Internal method. It should be used only to externalize initial item's values.
285    * This method never returns {@code null}.
286    */
287   HashMap<String, StringDescriptor> getInitialValues(){
288     return myPropertyName2initialValue;
289   }
290
291   public boolean isAutoCreateBinding() {
292     return myAutoCreateBinding;
293   }
294
295   public void setAutoCreateBinding(final boolean autoCreateBinding) {
296     myAutoCreateBinding = autoCreateBinding;
297   }
298
299   public boolean isCanAttachLabel() {
300     return myCanAttachLabel;
301   }
302
303   public void setCanAttachLabel(final boolean canAttachLabel) {
304     myCanAttachLabel = canAttachLabel;
305   }
306
307   public boolean isContainer() {
308     return myIsContainer;
309   }
310
311   public void setIsContainer(final boolean isContainer) {
312     myIsContainer = isContainer;
313   }
314
315   public boolean equals(final Object o) {
316     if (this == o) return true;
317     if (!(o instanceof ComponentItem)) return false;
318
319     final ComponentItem componentItem = (ComponentItem)o;
320
321     if (myClassName != null ? !myClassName.equals(componentItem.myClassName) : componentItem.myClassName != null) return false;
322     if (myDefaultConstraints != null
323         ? !myDefaultConstraints.equals(componentItem.myDefaultConstraints)
324         : componentItem.myDefaultConstraints != null) {
325       return false;
326     }
327     if (myIconPath != null ? !myIconPath.equals(componentItem.myIconPath) : componentItem.myIconPath != null) return false;
328     if (myPropertyName2initialValue != null
329         ? !myPropertyName2initialValue.equals(componentItem.myPropertyName2initialValue)
330         : componentItem.myPropertyName2initialValue != null) {
331       return false;
332     }
333     if (myToolTipText != null ? !myToolTipText.equals(componentItem.myToolTipText) : componentItem.myToolTipText != null) return false;
334
335     return true;
336   }
337
338   public int hashCode() {
339     int result;
340     result = (myClassName != null ? myClassName.hashCode() : 0);
341     result = 29 * result + (myDefaultConstraints != null ? myDefaultConstraints.hashCode() : 0);
342     result = 29 * result + (myIconPath != null ? myIconPath.hashCode() : 0);
343     result = 29 * result + (myToolTipText != null ? myToolTipText.hashCode() : 0);
344     result = 29 * result + (myPropertyName2initialValue != null ? myPropertyName2initialValue.hashCode() : 0);
345     return result;
346   }
347
348   public void customizeCellRenderer(ColoredListCellRenderer cellRenderer, boolean selected, boolean hasFocus) {
349     cellRenderer.setIcon(getSmallIcon());
350     if (myAnyComponent) {
351       cellRenderer.append(UIDesignerBundle.message("palette.non.palette.component"), SimpleTextAttributes.REGULAR_ATTRIBUTES);
352       cellRenderer.setToolTipText(UIDesignerBundle.message("palette.non.palette.component.tooltip"));
353     }
354     else {
355       cellRenderer.append(getClassShortName(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
356       cellRenderer.setToolTipText(getToolTipText());
357     }
358   }
359
360   @Nullable public DnDDragStartBean startDragging() {
361     if (isAnyComponent()) return null;
362     return new DnDDragStartBean(this);
363   }
364
365   @Nullable public ActionGroup getPopupActionGroup() {
366     return (ActionGroup) ActionManager.getInstance().getAction("GuiDesigner.PaletteComponentPopupMenu");
367   }
368
369   @Nullable public Object getData(Project project, String dataId) {
370     if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
371       return JavaPsiFacade.getInstance(project).findClass(myClassName, GlobalSearchScope.allScope(project));
372     }
373     if (getClass().getName().equals(dataId)) {
374       return this;
375     }
376     if (GroupItem.DATA_KEY.is(dataId)) {
377       return Palette.getInstance(project).findGroup(this);
378     }
379     return null;
380   }
381
382   @Nullable public PsiFile getBoundForm() {
383     if (myClassName.length() == 0 || myClassName.startsWith("javax.swing")) {
384       return null;
385     }
386     List<PsiFile> boundForms = FormClassIndex.findFormsBoundToClass(myProject, myClassName.replace('$', '.'));
387     if (boundForms.size() > 0) {
388       return boundForms.get(0);
389     }
390     return null;
391   }
392
393   @NotNull
394   public Dimension getInitialSize(final JComponent parent, final ClassLoader loader) {
395     if (myInitialSize != null) {
396       return myInitialSize;
397     }
398     myInitialSize = new Dimension(myDefaultConstraints.myPreferredSize);
399     if (myInitialSize.width <= 0 || myInitialSize.height <= 0) {
400       try {
401         Class aClass = Class.forName(getClassName(), true, loader);
402         RadAtomicComponent component = new RadAtomicComponent(aClass, "", Palette.getInstance(myProject));
403         component.initDefaultProperties(this);
404         final JComponent delegee = component.getDelegee();
405         if (parent != null) {
406           final Font font = parent.getFont();
407           delegee.setFont(font);
408         }
409         Dimension prefSize = delegee.getPreferredSize();
410         Dimension minSize = delegee.getMinimumSize();
411         if (myInitialSize.width <= 0) {
412           myInitialSize.width = prefSize.width;
413         }
414         if (myInitialSize.height <= 0) {
415           myInitialSize.height = prefSize.height;
416         }
417         myInitialSize.width = Math.max(myInitialSize.width, minSize.width);
418         myInitialSize.height = Math.max(myInitialSize.height, minSize.height);
419       }
420       catch (Exception e) {
421         LOG.debug(e);
422       }
423     }
424     return myInitialSize;
425   }
426
427   public static ComponentItem createAnyComponentItem(final Project project) {
428     ComponentItem result = new ComponentItem(project, "", null, null,
429                                              new GridConstraints(), new HashMap<>(),
430                                              false, false, false);
431     result.myAnyComponent = true;
432     return result;
433   }
434
435   public boolean isAnyComponent() {
436     return myAnyComponent;
437   }
438
439   public boolean isSpacer() {
440     return myClassName.equals(HSpacer.class.getName()) || myClassName.equals(VSpacer.class.getName());
441   }
442
443   private static final class MySmallIcon implements Icon{
444     private final Image myImage;
445
446     public MySmallIcon(@NotNull final Image delegate) {
447       myImage = delegate;
448     }
449
450     public int getIconHeight() {
451       return 18;
452     }
453
454     public int getIconWidth() {
455       return 18;
456     }
457
458     public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
459       g.drawImage(myImage, 2, 2, 14, 14, c);
460     }
461   }
462 }