blob: 48a4bd938aa8ce9a31c4b3b594c1efaa188493aa [file] [log] [blame]
/*
* Copyright 2000-2013 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.componentTree;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DeleteProvider;
import com.intellij.ide.highlighter.JavaHighlightingColors;
import com.intellij.ide.util.EditSourceUtil;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.uiDesigner.*;
import com.intellij.uiDesigner.actions.StartInplaceEditingAction;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.designSurface.*;
import com.intellij.uiDesigner.editor.UIFormEditor;
import com.intellij.uiDesigner.lw.LwInspectionSuppression;
import com.intellij.uiDesigner.palette.ComponentItem;
import com.intellij.uiDesigner.palette.Palette;
import com.intellij.uiDesigner.quickFixes.QuickFixManager;
import com.intellij.uiDesigner.radComponents.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.PlatformColors;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import icons.UIDesignerIcons;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.TreeUI;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public final class ComponentTree extends Tree implements DataProvider {
private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.componentTree.ComponentTree");
public static final DataKey<LwInspectionSuppression[]> LW_INSPECTION_SUPPRESSION_ARRAY_DATA_KEY =
DataKey.create(LwInspectionSuppression.class.getName());
private SimpleTextAttributes myBindingAttributes; // exists only for performance reason
private SimpleTextAttributes myClassAttributes; // exists only for performance reason
private SimpleTextAttributes myPackageAttributes; // exists only for performance reason
private SimpleTextAttributes myUnknownAttributes; // exists only for performance reason
private SimpleTextAttributes myTitleAttributes; // exists only for performance reason
private Map<HighlightSeverity, Map<SimpleTextAttributes, SimpleTextAttributes>> myHighlightAttributes;
private GuiEditor myEditor;
private UIFormEditor myFormEditor;
private QuickFixManager myQuickFixManager;
private RadComponent myDropTargetComponent = null;
private final StartInplaceEditingAction myStartInplaceEditingAction;
private final MyDeleteProvider myDeleteProvider = new MyDeleteProvider();
@NonNls private static final String ourHelpID = "guiDesigner.uiTour.compsTree";
private final Project myProject;
public ComponentTree(@NotNull final Project project) {
super(new DefaultTreeModel(new DefaultMutableTreeNode()));
myProject = project;
setCellRenderer(new MyTreeCellRenderer());
setRootVisible(false);
setShowsRootHandles(true);
// Enable tooltips
ToolTipManager.sharedInstance().registerComponent(this);
// Install convenient keyboard navigation
TreeUtil.installActions(this);
// Popup menu
PopupHandler.installPopupHandler(
this,
(ActionGroup)ActionManager.getInstance().getAction(IdeActions.GROUP_GUI_DESIGNER_COMPONENT_TREE_POPUP),
ActionPlaces.GUI_DESIGNER_COMPONENT_TREE_POPUP, ActionManager.getInstance());
// F2 should start inplace editing
myStartInplaceEditingAction = new StartInplaceEditingAction(null);
myStartInplaceEditingAction.registerCustomShortcutSet(
new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)),
this
);
if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
setDragEnabled(true);
setTransferHandler(new TransferHandler() {
public int getSourceActions(JComponent c) {
return DnDConstants.ACTION_COPY_OR_MOVE;
}
protected Transferable createTransferable(JComponent c) {
return DraggedComponentList.pickupSelection(myEditor, null);
}
});
setDropTarget(new DropTarget(this, new MyDropTargetListener()));
}
}
@NotNull
public Project getProject() {
return myProject;
}
public void initQuickFixManager(JViewport viewPort) {
myQuickFixManager = new QuickFixManagerImpl(null, this, viewPort);
}
public void setEditor(final GuiEditor editor) {
myEditor = editor;
myDeleteProvider.setEditor(editor);
myQuickFixManager.setEditor(editor);
myStartInplaceEditingAction.setEditor(editor);
}
public void refreshIntentionHint() {
myQuickFixManager.refreshIntentionHint();
}
@Nullable
public String getToolTipText(final MouseEvent e) {
final TreePath path = getPathForLocation(e.getX(), e.getY());
final RadComponent component = getComponentFromPath(path);
if (component != null) {
final ErrorInfo errorInfo = ErrorAnalyzer.getErrorForComponent(component);
if (errorInfo != null) {
return errorInfo.myDescription;
}
}
return null;
}
@Nullable
private static RadComponent getComponentFromPath(TreePath path) {
if (path != null) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
LOG.assertTrue(node != null);
final Object userObject = node.getUserObject();
if (userObject instanceof ComponentPtrDescriptor) {
final ComponentPtrDescriptor descriptor = (ComponentPtrDescriptor)userObject;
final ComponentPtr ptr = descriptor.getElement();
if (ptr != null && ptr.isValid()) {
final RadComponent component = ptr.getComponent();
LOG.assertTrue(component != null);
return component;
}
}
}
return null;
}
/**
* TODO[vova] should return pair <RadComponent, TreePath>
*
* @return first selected component. The method returns <code>null</code>
* if there is no selection in the tree.
*/
@Nullable
public RadComponent getSelectedComponent() {
return ArrayUtil.getFirstElement(getSelectedComponents());
}
/**
* TODO[vova] should return pair <RadComponent, TreePath>
*
* @return currently selected components.
*/
@NotNull public RadComponent[] getSelectedComponents() {
final TreePath[] paths = getSelectionPaths();
if (paths == null) {
return RadComponent.EMPTY_ARRAY;
}
final ArrayList<RadComponent> result = new ArrayList<RadComponent>(paths.length);
for (TreePath path : paths) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
if (node != null && node.getUserObject() instanceof ComponentPtrDescriptor) {
final ComponentPtrDescriptor descriptor = (ComponentPtrDescriptor)node.getUserObject();
final ComponentPtr ptr = descriptor.getElement();
if (ptr != null && ptr.isValid()) {
result.add(ptr.getComponent());
}
}
}
return result.toArray(new RadComponent[result.size()]);
}
/**
* Provides {@link PlatformDataKeys#NAVIGATABLE} to navigate to
* binding of currently selected component (if any)
*/
public Object getData(final String dataId) {
if (GuiEditor.DATA_KEY.is(dataId)) {
return myEditor;
}
if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
return myDeleteProvider;
}
if (PlatformDataKeys.COPY_PROVIDER.is(dataId) ||
PlatformDataKeys.CUT_PROVIDER.is(dataId) ||
PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
return myEditor == null ? null : myEditor.getData(dataId);
}
if (LW_INSPECTION_SUPPRESSION_ARRAY_DATA_KEY.is(dataId)) {
Collection<LwInspectionSuppression> elements = getSelectedElements(LwInspectionSuppression.class);
return elements.size() == 0 ? null : elements.toArray(new LwInspectionSuppression[elements.size()]);
}
if (PlatformDataKeys.HELP_ID.is(dataId)) {
return ourHelpID;
}
if (PlatformDataKeys.FILE_EDITOR.is(dataId)) {
return myFormEditor;
}
if (!CommonDataKeys.NAVIGATABLE.is(dataId)) {
return null;
}
final RadComponent selectedComponent = getSelectedComponent();
if (selectedComponent == null) {
return null;
}
final String classToBind = myEditor.getRootContainer().getClassToBind();
if (classToBind == null) {
return null;
}
final PsiClass aClass = FormEditingUtil.findClassToBind(myEditor.getModule(), classToBind);
if (aClass == null) {
return null;
}
if (selectedComponent instanceof RadRootContainer) {
return EditSourceUtil.getDescriptor(aClass);
}
final String binding = selectedComponent.getBinding();
if (binding == null) {
return null;
}
final PsiField[] fields = aClass.getFields();
for (final PsiField field : fields) {
if (binding.equals(field.getName())) {
return EditSourceUtil.getDescriptor(field);
}
}
return null;
}
public <T> List<T> getSelectedElements(Class<? extends T> elementClass) {
final TreePath[] paths = getSelectionPaths();
if (paths == null) {
return Collections.emptyList();
}
final ArrayList<T> result = new ArrayList<T>(paths.length);
for (TreePath path : paths) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
Object userObject = node.getUserObject();
if (userObject instanceof NodeDescriptor && elementClass.isInstance(((NodeDescriptor) userObject).getElement())) {
//noinspection unchecked
result.add((T)((NodeDescriptor) node.getUserObject()).getElement());
}
}
return result;
}
private SimpleTextAttributes getAttribute(@NotNull final SimpleTextAttributes attrs,
@Nullable HighlightDisplayLevel level) {
if (level == null) {
return attrs;
}
Map<SimpleTextAttributes, SimpleTextAttributes> highlightMap = myHighlightAttributes.get(level.getSeverity());
if (highlightMap == null) {
highlightMap = new HashMap<SimpleTextAttributes, SimpleTextAttributes>();
myHighlightAttributes.put(level.getSeverity(), highlightMap);
}
SimpleTextAttributes result = highlightMap.get(attrs);
if (result == null) {
final TextAttributesKey attrKey = SeverityRegistrar.getSeverityRegistrar(myProject).getHighlightInfoTypeBySeverity(level.getSeverity()).getAttributesKey();
TextAttributes textAttrs = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(attrKey);
textAttrs = TextAttributes.merge(attrs.toTextAttributes(), textAttrs);
result = SimpleTextAttributes.fromTextAttributes(textAttrs);
highlightMap.put(attrs, result);
}
return result;
}
public void setUI(final TreeUI ui) {
super.setUI(ui);
// [vova] we cannot create this hash in constructor and just clear it here. The
// problem is that setUI is invoked by constructor of superclass.
myHighlightAttributes = new HashMap<HighlightSeverity, Map<SimpleTextAttributes, SimpleTextAttributes>>();
final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
final TextAttributes attributes = globalScheme.getAttributes(JavaHighlightingColors.STRING);
myBindingAttributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, UIUtil.getTreeForeground());
myClassAttributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, UIUtil.getTreeForeground());
myPackageAttributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, Color.GRAY);
myTitleAttributes =new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, attributes.getForegroundColor());
myUnknownAttributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_WAVED, Color.RED);
}
public static Icon getComponentIcon(final RadComponent component) {
if (!(component instanceof RadErrorComponent)) {
final Palette palette = Palette.getInstance(component.getProject());
final ComponentItem item = palette.getItem(component.getComponentClassName());
final Icon icon;
if (item != null) {
icon = item.getSmallIcon();
}
else {
icon = UIDesignerIcons.Unknown_small;
}
return icon;
}
else {
return AllIcons.General.Error;
}
}
public void setDropTargetComponent(final @Nullable RadComponent dropTargetComponent) {
if (dropTargetComponent != myDropTargetComponent) {
myDropTargetComponent = dropTargetComponent;
repaint();
}
}
public void setFormEditor(final UIFormEditor formEditor) {
myFormEditor = formEditor;
}
private final class MyTreeCellRenderer extends ColoredTreeCellRenderer {
@NonNls private static final String SWING_PACKAGE = "javax.swing";
public void customizeCellRenderer(
final JTree tree,
final Object value,
final boolean selected,
final boolean expanded,
final boolean leaf,
final int row,
final boolean hasFocus
) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
if (node.getUserObject() instanceof ComponentPtrDescriptor) {
final ComponentPtrDescriptor descriptor = (ComponentPtrDescriptor)node.getUserObject();
final ComponentPtr ptr = descriptor.getElement();
if (ptr == null) return;
final RadComponent component = ptr.getComponent();
if (component == null) return;
final HighlightDisplayLevel level = ErrorAnalyzer.getHighlightDisplayLevel(myProject, component);
// Text
boolean hasText = false;
final String binding = component.getBinding();
if (binding != null) {
append(binding, getAttribute(myBindingAttributes, level));
append(" : ", getAttribute(myClassAttributes, level));
hasText = true;
}
else {
String componentTitle = component.getComponentTitle();
if (componentTitle != null) {
append(componentTitle, getAttribute(myTitleAttributes, level));
append(" : ", getAttribute(myClassAttributes, level));
hasText = true;
}
}
final String componentClassName = component.getComponentClassName();
if (component instanceof RadVSpacer) {
append(UIDesignerBundle.message("component.vertical.spacer"), getAttribute(myClassAttributes, level));
}
else if (component instanceof RadHSpacer) {
append(UIDesignerBundle.message("component.horizontal.spacer"), getAttribute(myClassAttributes, level));
}
else if (component instanceof RadErrorComponent) {
final RadErrorComponent c = (RadErrorComponent)component;
append(c.getErrorDescription(), getAttribute(myUnknownAttributes, level));
}
else if (component instanceof RadRootContainer) {
append(UIDesignerBundle.message("component.form"), getAttribute(myClassAttributes, level));
append(" (", getAttribute(myPackageAttributes, level));
final String classToBind = ((RadRootContainer)component).getClassToBind();
if (classToBind != null) {
append(classToBind, getAttribute(myPackageAttributes, level));
}
else {
append(UIDesignerBundle.message("component.no.binding"), getAttribute(myPackageAttributes, level));
}
append(")", getAttribute(myPackageAttributes, level));
}
else {
String packageName = null;
int pos = componentClassName.lastIndexOf('.');
if (pos >= 0) {
packageName = componentClassName.substring(0, pos);
}
SimpleTextAttributes classAttributes = hasText ? myPackageAttributes : myClassAttributes;
if (packageName != null) {
append(componentClassName.substring(packageName.length() + 1).replace('$', '.'),
getAttribute(classAttributes, level));
if (!packageName.equals(SWING_PACKAGE)) {
append(" (", getAttribute(myPackageAttributes, level));
append(packageName, getAttribute(myPackageAttributes, level));
append(")", getAttribute(myPackageAttributes, level));
}
}
else {
append(componentClassName.replace('$', '.'), getAttribute(classAttributes, level));
}
}
// Icon
setIcon(getComponentIcon(component));
if (component == myDropTargetComponent) {
setBorder(BorderFactory.createLineBorder(PlatformColors.BLUE, 1));
} else {
setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
}
}
else if (node.getUserObject() != null) {
final String fragment = node.getUserObject().toString();
if (fragment != null) {
append(fragment, SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
if (node.getUserObject() instanceof SuppressionDescriptor) {
setIcon(UIDesignerIcons.InspectionSuppression);
}
else if (node.getUserObject() instanceof ButtonGroupDescriptor) {
setIcon(UIDesignerIcons.ButtonGroup);
}
}
}
}
private final class MyDropTargetListener extends DropTargetAdapter {
public void dragOver(DropTargetDragEvent dtde) {
try {
RadComponent dropTargetComponent = null;
ComponentDragObject dragObject = null;
final DraggedComponentList dcl = DraggedComponentList.fromTransferable(dtde.getTransferable());
if (dcl != null) {
dragObject = dcl;
}
else {
ComponentItem componentItem = SimpleTransferable.getData(dtde.getTransferable(), ComponentItem.class);
if (componentItem != null) {
dragObject = new ComponentItemDragObject(componentItem);
}
}
boolean canDrop = false;
if (dragObject != null) {
final TreePath path = getPathForLocation((int) dtde.getLocation().getX(),
(int) dtde.getLocation().getY());
final RadComponent targetComponent = getComponentFromPath(path);
if (path != null && targetComponent instanceof RadContainer) {
final ComponentDropLocation dropLocation = ((RadContainer)targetComponent).getDropLocation(null);
canDrop = dropLocation.canDrop(dragObject);
if (dcl != null && FormEditingUtil.isDropOnChild(dcl, dropLocation)) {
canDrop = false;
}
if (canDrop) {
dropTargetComponent = targetComponent;
dtde.acceptDrag(dtde.getDropAction());
}
}
}
if (!canDrop) {
dtde.rejectDrag();
}
setDropTargetComponent(dropTargetComponent);
}
catch (Exception e) {
LOG.error(e);
}
}
public void dragExit(DropTargetEvent dte) {
setDropTargetComponent(null);
}
public void drop(DropTargetDropEvent dtde) {
try {
final DraggedComponentList dcl = DraggedComponentList.fromTransferable(dtde.getTransferable());
ComponentItem componentItem = SimpleTransferable.getData(dtde.getTransferable(), ComponentItem.class);
if (dcl != null || componentItem != null) {
final TreePath path = getPathForLocation((int) dtde.getLocation().getX(),
(int) dtde.getLocation().getY());
final RadComponent targetComponent = getComponentFromPath(path);
if (!myEditor.ensureEditable()) return;
if (targetComponent instanceof RadContainer) {
final ComponentDropLocation dropLocation = ((RadContainer)targetComponent).getDropLocation(null);
if (dcl != null) {
if (!FormEditingUtil.isDropOnChild(dcl, dropLocation)) {
RadComponent[] components = dcl.getComponents().toArray(new RadComponent [dcl.getComponents().size()]);
RadContainer[] originalParents = dcl.getOriginalParents();
final GridConstraints[] originalConstraints = dcl.getOriginalConstraints();
for(int i=0; i<components.length; i++) {
originalParents [i].removeComponent(components [i]);
}
dropLocation.processDrop(myEditor, components, null, dcl);
for (int i = 0; i < originalConstraints.length; i++) {
if (originalParents[i].getLayoutManager().isGrid()) {
FormEditingUtil.deleteEmptyGridCells(originalParents[i], originalConstraints[i]);
}
}
}
}
else {
new InsertComponentProcessor(myEditor).processComponentInsert(componentItem, dropLocation);
}
}
myEditor.refreshAndSave(true);
}
setDropTargetComponent(null);
}
catch (Exception e) {
LOG.error(e);
}
}
}
private static class MyDeleteProvider implements DeleteProvider {
private GuiEditor myEditor;
public void setEditor(final GuiEditor editor) {
myEditor = editor;
}
public void deleteElement(@NotNull DataContext dataContext) {
if (myEditor != null) {
LwInspectionSuppression[] suppressions = LW_INSPECTION_SUPPRESSION_ARRAY_DATA_KEY.getData(dataContext);
if (suppressions != null) {
if (!myEditor.ensureEditable()) return;
for(LwInspectionSuppression suppression: suppressions) {
myEditor.getRootContainer().removeInspectionSuppression(suppression);
}
myEditor.refreshAndSave(true);
}
else {
DeleteProvider baseProvider = (DeleteProvider) myEditor.getData(PlatformDataKeys.DELETE_ELEMENT_PROVIDER.getName());
if (baseProvider != null) {
baseProvider.deleteElement(dataContext);
}
}
}
}
public boolean canDeleteElement(@NotNull DataContext dataContext) {
if (myEditor != null) {
LwInspectionSuppression[] suppressions = LW_INSPECTION_SUPPRESSION_ARRAY_DATA_KEY.getData(dataContext);
if (suppressions != null) {
return true;
}
DeleteProvider baseProvider = (DeleteProvider) myEditor.getData(PlatformDataKeys.DELETE_ELEMENT_PROVIDER.getName());
if (baseProvider != null) {
return baseProvider.canDeleteElement(dataContext);
}
}
return false;
}
}
}