/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.uiDesigner.palette; import com.intellij.ide.dnd.DnDDragStartBean; import com.intellij.ide.palette.PaletteItem; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataKey; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.ResourceFileUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiFile; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.ui.ColoredListCellRenderer; import com.intellij.ui.SimpleTextAttributes; import com.intellij.uiDesigner.HSpacer; import com.intellij.uiDesigner.UIDesignerBundle; import com.intellij.uiDesigner.VSpacer; import com.intellij.uiDesigner.binding.FormClassIndex; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.lw.StringDescriptor; import com.intellij.uiDesigner.propertyInspector.IntrospectedProperty; import com.intellij.uiDesigner.radComponents.RadAtomicComponent; import icons.UIDesignerIcons; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.io.IOException; import java.util.HashMap; import java.util.List; /** * @author Anton Katilin * @author Vladimir Kondratyev */ public final class ComponentItem implements Cloneable, PaletteItem { private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.palette.ComponentItem"); public static final DataKey<ComponentItem> DATA_KEY = DataKey.create(ComponentItem.class.getName()); @NonNls private String myClassName; private final GridConstraints myDefaultConstraints; /** * Do not use this member directly. Use {@link #getIcon()} instead. */ private Icon myIcon; /** * Do not use this member directly. Use {@link #getSmallIcon()} instead. */ private Icon mySmallIcon; /** * @see #getIconPath() * @see #setIconPath(java.lang.String) */ private String myIconPath; /** * Do not access this field directly. Use {@link #getToolTipText()} instead. */ final String myToolTipText; private final HashMap<String, StringDescriptor> myPropertyName2initialValue; /** Whether item is removable or not */ private final boolean myRemovable; private boolean myAutoCreateBinding; private boolean myCanAttachLabel; private boolean myIsContainer; private boolean myAnyComponent; private Dimension myInitialSize; @NotNull private final Project myProject; public ComponentItem( @NotNull Project project, @NotNull final String className, @Nullable final String iconPath, @Nullable final String toolTipText, @NotNull final GridConstraints defaultConstraints, @NotNull final HashMap<String, StringDescriptor> propertyName2initialValue, final boolean removable, final boolean autoCreateBinding, final boolean canAttachLabel ){ myAutoCreateBinding = autoCreateBinding; myCanAttachLabel = canAttachLabel; myProject = project; setClassName(className); setIconPath(iconPath); myToolTipText = toolTipText; myDefaultConstraints = defaultConstraints; myPropertyName2initialValue = propertyName2initialValue; myRemovable = removable; } /** * @return whether the item is removable from palette or not. */ public boolean isRemovable() { return myRemovable; } private static String calcToolTipText(@NotNull final String className) { final int lastDotIndex = className.lastIndexOf('.'); if (lastDotIndex != -1 && lastDotIndex != className.length() - 1/*not the last char in class name*/) { return className.substring(lastDotIndex + 1) + " (" + className.substring(0, lastDotIndex) + ")"; } else{ return className; } } /** Creates deep copy of the object. You can edit any properties of the returned object. */ public ComponentItem clone(){ final ComponentItem result = new ComponentItem( myProject, myClassName, myIconPath, myToolTipText, (GridConstraints)myDefaultConstraints.clone(), (HashMap<String, StringDescriptor>)myPropertyName2initialValue.clone(), myRemovable, myAutoCreateBinding, myCanAttachLabel ); result.setIsContainer(myIsContainer); return result; } /** * @return string that represents path in the JAR file system that was used to load * icon returned by {@link #getIcon()} method. This method can returns <code>null</code>. * It means that palette item has some "unknown" item. */ @Nullable String getIconPath() { return myIconPath; } /** * @param iconPath new path inside JAR file system. <code>null</code> means that * <code>iconPath</code> is not specified and some "unknown" icon should be used * to represent the {@link ComponentItem} in UI. */ void setIconPath(@Nullable final String iconPath){ myIcon = null; // reset cached icon mySmallIcon = null; // reset cached icon myIconPath = iconPath; } /** * @return item's icon. This icon is used to represent item at the toolbar. * Note, that the method never returns <code>null</code>. It returns some * default "unknown" icon for the items that has no specified icon in the XML. */ @NotNull public Icon getIcon() { // Check cached value first if(myIcon != null){ return myIcon; } // Create new icon if(myIconPath != null && myIconPath.length() > 0) { final VirtualFile iconFile = ResourceFileUtil.findResourceFileInScope(myIconPath, myProject, GlobalSearchScope.allScope(myProject)); if (iconFile != null) { try { myIcon = new ImageIcon(iconFile.contentsToByteArray()); } catch (IOException e) { myIcon = null; } } else { myIcon = IconLoader.findIcon(myIconPath); } } if(myIcon == null){ myIcon = UIDesignerIcons.Unknown; } LOG.assertTrue(myIcon != null); return myIcon; } /** * @return small item's icon. This icon represents component in the * component tree. The method never returns <code>null</code>. It returns some * default "unknown" icon for the items that has no specified icon in the XML. */ @NotNull public Icon getSmallIcon() { // Check cached value first if(mySmallIcon != null){ return myIcon; } // [vova] It's safe to cast to ImageIcon here because all icons loaded by IconLoader // are ImageIcon(s). final Icon icon = getIcon(); if (icon instanceof ImageIcon) { final ImageIcon imageIcon = (ImageIcon)icon; mySmallIcon = new MySmallIcon(imageIcon.getImage()); } else { mySmallIcon = icon; } return mySmallIcon; } /** * @return name of component's class which is represented by the item. */ @NotNull public String getClassName() { return myClassName; } public String getClassShortName() { final int lastDotIndex = myClassName.lastIndexOf('.'); if (lastDotIndex != -1 && lastDotIndex != myClassName.length() - 1/*not the last char in class name*/) { return myClassName.substring(lastDotIndex + 1).replace('$', '.'); } else{ return myClassName.replace('$', '.'); } } /** * @param className name of the class that will be instanteated when user drop * item on the form. Cannot be <code>null</code>. If the class does not exist or * could not be instanteated (for example, class has no default constructor, * it's not a subclass of JComponent, etc) then placeholder component will be * added to the form. */ public void setClassName(@NotNull final String className){ myClassName = className; } public String getToolTipText() { return myToolTipText != null ? myToolTipText : calcToolTipText(myClassName); } @NotNull public GridConstraints getDefaultConstraints() { return myDefaultConstraints; } /** * The method returns initial value of the property. Term * "initial" means that just after creation of RadComponent * all its properties are set into initial values. * The method returns <code>null</code> if the * initial property is not defined. Unfortunately we cannot * put this method into the constuctor of <code>RadComponent</code>. * The problem is that <code>RadComponent</code> is used in the * code genaration and code generation doesn't depend on any * <code>ComponentItem</code>, so we need to initialize <code>RadComponent</code> * in all places where it's needed explicitly. */ public Object getInitialValue(final IntrospectedProperty property){ return myPropertyName2initialValue.get(property.getName()); } /** * Internal method. It should be used only to externalize initial item's values. * This method never returns <code>null</code>. */ HashMap<String, StringDescriptor> getInitialValues(){ return myPropertyName2initialValue; } public boolean isAutoCreateBinding() { return myAutoCreateBinding; } public void setAutoCreateBinding(final boolean autoCreateBinding) { myAutoCreateBinding = autoCreateBinding; } public boolean isCanAttachLabel() { return myCanAttachLabel; } public void setCanAttachLabel(final boolean canAttachLabel) { myCanAttachLabel = canAttachLabel; } public boolean isContainer() { return myIsContainer; } public void setIsContainer(final boolean isContainer) { myIsContainer = isContainer; } public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof ComponentItem)) return false; final ComponentItem componentItem = (ComponentItem)o; if (myClassName != null ? !myClassName.equals(componentItem.myClassName) : componentItem.myClassName != null) return false; if (myDefaultConstraints != null ? !myDefaultConstraints.equals(componentItem.myDefaultConstraints) : componentItem.myDefaultConstraints != null) { return false; } if (myIconPath != null ? !myIconPath.equals(componentItem.myIconPath) : componentItem.myIconPath != null) return false; if (myPropertyName2initialValue != null ? !myPropertyName2initialValue.equals(componentItem.myPropertyName2initialValue) : componentItem.myPropertyName2initialValue != null) { return false; } if (myToolTipText != null ? !myToolTipText.equals(componentItem.myToolTipText) : componentItem.myToolTipText != null) return false; return true; } public int hashCode() { int result; result = (myClassName != null ? myClassName.hashCode() : 0); result = 29 * result + (myDefaultConstraints != null ? myDefaultConstraints.hashCode() : 0); result = 29 * result + (myIconPath != null ? myIconPath.hashCode() : 0); result = 29 * result + (myToolTipText != null ? myToolTipText.hashCode() : 0); result = 29 * result + (myPropertyName2initialValue != null ? myPropertyName2initialValue.hashCode() : 0); return result; } public void customizeCellRenderer(ColoredListCellRenderer cellRenderer, boolean selected, boolean hasFocus) { cellRenderer.setIcon(getSmallIcon()); if (myAnyComponent) { cellRenderer.append(UIDesignerBundle.message("palette.non.palette.component"), SimpleTextAttributes.REGULAR_ATTRIBUTES); cellRenderer.setToolTipText(UIDesignerBundle.message("palette.non.palette.component.tooltip")); } else { cellRenderer.append(getClassShortName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); cellRenderer.setToolTipText(getToolTipText()); } } @Nullable public DnDDragStartBean startDragging() { if (isAnyComponent()) return null; return new DnDDragStartBean(this); } @Nullable public ActionGroup getPopupActionGroup() { return (ActionGroup) ActionManager.getInstance().getAction("GuiDesigner.PaletteComponentPopupMenu"); } @Nullable public Object getData(Project project, String dataId) { if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { return JavaPsiFacade.getInstance(project).findClass(myClassName, GlobalSearchScope.allScope(project)); } if (getClass().getName().equals(dataId)) { return this; } if (GroupItem.DATA_KEY.is(dataId)) { return Palette.getInstance(project).findGroup(this); } return null; } @Nullable public PsiFile getBoundForm() { if (myClassName.length() == 0 || myClassName.startsWith("javax.swing")) { return null; } List<PsiFile> boundForms = FormClassIndex.findFormsBoundToClass(myProject, myClassName.replace('$', '.')); if (boundForms.size() > 0) { return boundForms.get(0); } return null; } @NotNull public Dimension getInitialSize(final JComponent parent, final ClassLoader loader) { if (myInitialSize != null) { return myInitialSize; } myInitialSize = new Dimension(myDefaultConstraints.myPreferredSize); if (myInitialSize.width <= 0 || myInitialSize.height <= 0) { try { Class aClass = Class.forName(getClassName(), true, loader); RadAtomicComponent component = new RadAtomicComponent(aClass, "", Palette.getInstance(myProject)); component.initDefaultProperties(this); final JComponent delegee = component.getDelegee(); if (parent != null) { final Font font = parent.getFont(); delegee.setFont(font); } Dimension prefSize = delegee.getPreferredSize(); Dimension minSize = delegee.getMinimumSize(); if (myInitialSize.width <= 0) { myInitialSize.width = prefSize.width; } if (myInitialSize.height <= 0) { myInitialSize.height = prefSize.height; } myInitialSize.width = Math.max(myInitialSize.width, minSize.width); myInitialSize.height = Math.max(myInitialSize.height, minSize.height); } catch (Exception e) { LOG.debug(e); } } return myInitialSize; } public static ComponentItem createAnyComponentItem(final Project project) { ComponentItem result = new ComponentItem(project, "", null, null, new GridConstraints(), new HashMap<>(), false, false, false); result.myAnyComponent = true; return result; } public boolean isAnyComponent() { return myAnyComponent; } public boolean isSpacer() { return myClassName.equals(HSpacer.class.getName()) || myClassName.equals(VSpacer.class.getName()); } private static final class MySmallIcon implements Icon{ private final Image myImage; public MySmallIcon(@NotNull final Image delegate) { myImage = delegate; } public int getIconHeight() { return 18; } public int getIconWidth() { return 18; } public void paintIcon(final Component c, final Graphics g, final int x, final int y) { g.drawImage(myImage, 2, 2, 14, 14, c); } } }