com.intellij.uiDesigner.FormEditingUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.uiDesigner.FormEditingUtil.java

Source

/*
 * 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;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;

import org.consulo.psi.PsiPackage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.intellij.lang.properties.PropertiesReferenceManager;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.JavaDirectoryService;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.uiDesigner.compiler.AsmCodeGenerator;
import com.intellij.uiDesigner.componentTree.ComponentTreeBuilder;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.designSurface.ComponentDropLocation;
import com.intellij.uiDesigner.designSurface.DraggedComponentList;
import com.intellij.uiDesigner.designSurface.GuiEditor;
import com.intellij.uiDesigner.designSurface.Painter;
import com.intellij.uiDesigner.editor.UIFormEditor;
import com.intellij.uiDesigner.lw.IButtonGroup;
import com.intellij.uiDesigner.lw.IComponent;
import com.intellij.uiDesigner.lw.IContainer;
import com.intellij.uiDesigner.lw.IProperty;
import com.intellij.uiDesigner.lw.IRootContainer;
import com.intellij.uiDesigner.lw.ITabbedPane;
import com.intellij.uiDesigner.lw.StringDescriptor;
import com.intellij.uiDesigner.palette.ComponentItem;
import com.intellij.uiDesigner.palette.Palette;
import com.intellij.uiDesigner.propertyInspector.DesignerToolWindowManager;
import com.intellij.uiDesigner.propertyInspector.properties.BindingProperty;
import com.intellij.uiDesigner.propertyInspector.properties.IntroComponentProperty;
import com.intellij.uiDesigner.radComponents.RadAbstractGridLayoutManager;
import com.intellij.uiDesigner.radComponents.RadComponent;
import com.intellij.uiDesigner.radComponents.RadContainer;
import com.intellij.uiDesigner.radComponents.RadRootContainer;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashSet;

/**
 * @author Anton Katilin
 * @author Vladimir Kondratyev
 */
public final class FormEditingUtil {
    private FormEditingUtil() {
    }

    public static boolean canDeleteSelection(final GuiEditor editor) {
        final ArrayList<RadComponent> selection = getSelectedComponents(editor);
        if (selection.isEmpty()) {
            return false;
        }
        final RadRootContainer rootContainer = editor.getRootContainer();
        if (rootContainer.getComponentCount() > 0 && selection.contains(rootContainer.getComponent(0))) {
            return false;
        }
        return true;
    }

    /**
     * <b>This method must be executed in command</b>
     *
     * @param editor the editor in which the selection is deleted.
     */
    public static void deleteSelection(final GuiEditor editor) {
        final List<RadComponent> selection = getSelectedComponents(editor);
        deleteComponents(selection, true);
        editor.refreshAndSave(true);
    }

    public static void deleteComponents(final Collection<? extends RadComponent> selection,
            boolean deleteEmptyCells) {
        if (selection.size() == 0) {
            return;
        }
        final RadRootContainer rootContainer = (RadRootContainer) getRoot(selection.iterator().next());
        final Set<String> deletedComponentIds = new HashSet<String>();
        for (final RadComponent component : selection) {
            boolean wasSelected = component.isSelected();
            final RadContainer parent = component.getParent();

            boolean wasPackedHorz = false;
            boolean wasPackedVert = false;
            if (parent.getParent() != null && parent.getParent().isXY()) {
                final Dimension minSize = parent.getMinimumSize();
                wasPackedHorz = parent.getWidth() == minSize.width;
                wasPackedVert = parent.getHeight() == minSize.height;
            }

            iterate(component, new ComponentVisitor() {
                public boolean visit(final IComponent c) {
                    RadComponent rc = (RadComponent) c;
                    BindingProperty.checkRemoveUnusedField(rootContainer, rc.getBinding(), null);
                    deletedComponentIds.add(rc.getId());
                    return true;
                }
            });

            GridConstraints delConstraints = parent.getLayoutManager().isGrid() ? component.getConstraints() : null;

            int index = parent.indexOfComponent(component);
            parent.removeComponent(component);
            if (wasSelected) {
                if (parent.getComponentCount() > index) {
                    parent.getComponent(index).setSelected(true);
                } else if (index > 0 && parent.getComponentCount() == index) {
                    parent.getComponent(index - 1).setSelected(true);
                } else {
                    parent.setSelected(true);
                }
            }
            if (delConstraints != null && deleteEmptyCells) {
                deleteEmptyGridCells(parent, delConstraints);
            }

            if (wasPackedHorz || wasPackedVert) {
                final Dimension minSize = parent.getMinimumSize();
                Dimension newSize = new Dimension(parent.getWidth(), parent.getHeight());
                if (wasPackedHorz) {
                    newSize.width = minSize.width;
                }
                if (wasPackedVert) {
                    newSize.height = minSize.height;
                }
                parent.setSize(newSize);
            }
        }

        iterate(rootContainer, new ComponentVisitor() {
            public boolean visit(final IComponent component) {
                RadComponent rc = (RadComponent) component;
                for (IProperty p : component.getModifiedProperties()) {
                    if (p instanceof IntroComponentProperty) {
                        IntroComponentProperty icp = (IntroComponentProperty) p;
                        final String value = icp.getValue(rc);
                        if (deletedComponentIds.contains(value)) {
                            try {
                                icp.resetValue(rc);
                            } catch (Exception e) {
                                // ignore
                            }
                        }
                    }
                }
                return true;
            }
        });
    }

    public static void deleteEmptyGridCells(final RadContainer parent, final GridConstraints delConstraints) {
        deleteEmptyGridCells(parent, delConstraints, true);
        deleteEmptyGridCells(parent, delConstraints, false);
    }

    private static void deleteEmptyGridCells(final RadContainer parent, final GridConstraints delConstraints,
            final boolean isRow) {
        final RadAbstractGridLayoutManager layoutManager = parent.getGridLayoutManager();
        for (int cell = delConstraints.getCell(isRow) + delConstraints.getSpan(isRow) - 1; cell >= delConstraints
                .getCell(isRow); cell--) {
            if (cell < parent.getGridCellCount(isRow)
                    && GridChangeUtil.canDeleteCell(parent, cell, isRow) == GridChangeUtil.CellStatus.Empty
                    && !layoutManager.isGapCell(parent, isRow, cell)) {
                layoutManager.deleteGridCells(parent, cell, isRow);
            }
        }
    }

    public static final int EMPTY_COMPONENT_SIZE = 5;

    private static Component getDeepestEmptyComponentAt(JComponent parent, Point location) {
        int size = parent.getComponentCount();

        for (int i = 0; i < size; i++) {
            Component child = parent.getComponent(i);

            if (child.isShowing()) {
                if (child.getWidth() < EMPTY_COMPONENT_SIZE || child.getHeight() < EMPTY_COMPONENT_SIZE) {
                    Point childLocation = child.getLocationOnScreen();
                    Rectangle bounds = new Rectangle();

                    bounds.x = childLocation.x;
                    bounds.y = childLocation.y;
                    bounds.width = child.getWidth();
                    bounds.height = child.getHeight();
                    bounds.grow(child.getWidth() < EMPTY_COMPONENT_SIZE ? EMPTY_COMPONENT_SIZE : 0,
                            child.getHeight() < EMPTY_COMPONENT_SIZE ? EMPTY_COMPONENT_SIZE : 0);

                    if (bounds.contains(location)) {
                        return child;
                    }
                }

                if (child instanceof JComponent) {
                    Component result = getDeepestEmptyComponentAt((JComponent) child, location);

                    if (result != null) {
                        return result;
                    }
                }
            }
        }

        return null;
    }

    /**
     * @param x in editor pane coordinates
     * @param y in editor pane coordinates
     */
    public static RadComponent getRadComponentAt(final RadRootContainer rootContainer, final int x, final int y) {
        Point location = new Point(x, y);
        SwingUtilities.convertPointToScreen(location, rootContainer.getDelegee());
        Component c = getDeepestEmptyComponentAt(rootContainer.getDelegee(), location);

        if (c == null) {
            c = SwingUtilities.getDeepestComponentAt(rootContainer.getDelegee(), x, y);
        }

        RadComponent result = null;

        while (c != null) {
            if (c instanceof JComponent) {
                final RadComponent component = (RadComponent) ((JComponent) c)
                        .getClientProperty(RadComponent.CLIENT_PROP_RAD_COMPONENT);
                if (component != null) {

                    if (result == null) {
                        result = component;
                    } else {
                        final Point p = SwingUtilities.convertPoint(rootContainer.getDelegee(), x, y, c);
                        if (Painter.getResizeMask(component, p.x, p.y) != 0) {
                            result = component;
                        }
                    }
                }
            }
            c = c.getParent();
        }

        return result;
    }

    /**
     * @return component which has dragger. There is only one component with the dargger
     * at a time.
     */
    @Nullable
    public static RadComponent getDraggerHost(@NotNull final GuiEditor editor) {
        final Ref<RadComponent> result = new Ref<RadComponent>();
        iterate(editor.getRootContainer(), new ComponentVisitor<RadComponent>() {
            public boolean visit(final RadComponent component) {
                if (component.hasDragger()) {
                    result.set(component);
                    return false;
                }
                return true;
            }
        });

        return result.get();
    }

    public static Cursor getMoveDropCursor() {
        try {
            return Cursor.getSystemCustomCursor("MoveDrop.32x32");
        } catch (Exception ex) {
            return Cursor.getDefaultCursor();
        }
    }

    public static Cursor getMoveNoDropCursor() {
        try {
            return Cursor.getSystemCustomCursor("MoveNoDrop.32x32");
        } catch (Exception ex) {
            return Cursor.getDefaultCursor();
        }
    }

    public static Cursor getCopyDropCursor() {
        try {
            return Cursor.getSystemCustomCursor("CopyDrop.32x32");
        } catch (Exception ex) {
            return Cursor.getDefaultCursor();
        }
    }

    /**
     * @return currently selected components. Method returns the minimal amount of
     * selected component which contains all selected components. It means that if the
     * selected container contains some selected children then only this container
     * will be added to the returned array.
     */
    @NotNull
    public static ArrayList<RadComponent> getSelectedComponents(@NotNull final GuiEditor editor) {
        final ArrayList<RadComponent> result = new ArrayList<RadComponent>();
        calcSelectedComponentsImpl(result, editor.getRootContainer());
        return result;
    }

    private static void calcSelectedComponentsImpl(final ArrayList<RadComponent> result,
            final RadContainer container) {
        if (container.isSelected()) {
            if (container.getParent() != null) { // ignore RadRootContainer
                result.add(container);
                return;
            }
        }

        for (int i = 0; i < container.getComponentCount(); i++) {
            final RadComponent component = container.getComponent(i);
            if (component instanceof RadContainer) {
                calcSelectedComponentsImpl(result, (RadContainer) component);
            } else {
                if (component.isSelected()) {
                    result.add(component);
                }
            }
        }
    }

    /**
     * @return all selected component inside the <code>editor</code>
     */
    @NotNull
    public static ArrayList<RadComponent> getAllSelectedComponents(@NotNull final GuiEditor editor) {
        final ArrayList<RadComponent> result = new ArrayList<RadComponent>();
        iterate(editor.getRootContainer(), new ComponentVisitor<RadComponent>() {
            public boolean visit(final RadComponent component) {
                if (component.isSelected()) {
                    result.add(component);
                }
                return true;
            }
        });
        return result;
    }

    public static String getExceptionMessage(Throwable ex) {
        if (ex instanceof RuntimeException) {
            final Throwable cause = ex.getCause();
            if (cause != null && cause != ex) {
                return getExceptionMessage(cause);
            }
        } else if (ex instanceof InvocationTargetException) {
            final Throwable target = ((InvocationTargetException) ex).getTargetException();
            if (target != null && target != ex) {
                return getExceptionMessage(target);
            }
        }
        String message = ex.getMessage();
        if (ex instanceof ClassNotFoundException) {
            message = message != null ? UIDesignerBundle.message("error.class.not.found.N", message)
                    : UIDesignerBundle.message("error.class.not" + ".found");
        } else if (ex instanceof NoClassDefFoundError) {
            message = message != null ? UIDesignerBundle.message("error.required.class.not.found.N", message)
                    : UIDesignerBundle.message("error" + ".required.class.not.found");
        }
        return message;
    }

    public static IComponent findComponentWithBinding(IComponent component, final String binding) {
        return findComponentWithBinding(component, binding, null);
    }

    public static IComponent findComponentWithBinding(IComponent component, final String binding,
            @Nullable final IComponent exceptComponent) {
        // Check that binding is unique
        final Ref<IComponent> boundComponent = new Ref<IComponent>();
        iterate(component, new ComponentVisitor() {
            public boolean visit(final IComponent component) {
                if (component != exceptComponent && binding.equals(component.getBinding())) {
                    boundComponent.set(component);
                    return false;
                }
                return true;
            }
        });

        return boundComponent.get();
    }

    @Nullable
    public static RadContainer getRadContainerAt(final RadRootContainer rootContainer, final int x, final int y,
            int epsilon) {
        RadComponent component = getRadComponentAt(rootContainer, x, y);
        if (isNullOrRoot(component) && epsilon > 0) {
            // try to find component near specified location
            component = getRadComponentAt(rootContainer, x - epsilon, y - epsilon);
            if (isNullOrRoot(component)) {
                component = getRadComponentAt(rootContainer, x - epsilon, y + epsilon);
            }
            if (isNullOrRoot(component)) {
                component = getRadComponentAt(rootContainer, x + epsilon, y - epsilon);
            }
            if (isNullOrRoot(component)) {
                component = getRadComponentAt(rootContainer, x + epsilon, y + epsilon);
            }
        }

        if (component != null) {
            return component instanceof RadContainer ? (RadContainer) component : component.getParent();
        }
        return null;
    }

    private static boolean isNullOrRoot(final RadComponent component) {
        return component == null || component instanceof RadRootContainer;
    }

    public static GridConstraints getDefaultConstraints(final RadComponent component) {
        final Palette palette = Palette.getInstance(component.getProject());
        final ComponentItem item = palette.getItem(component.getComponentClassName());
        if (item != null) {
            return item.getDefaultConstraints();
        }
        return new GridConstraints();
    }

    public static IRootContainer getRoot(IComponent component) {
        while (component != null) {
            if (component.getParentContainer() instanceof IRootContainer) {
                return (IRootContainer) component.getParentContainer();
            }
            component = component.getParentContainer();
        }
        return null;
    }

    /**
     * Iterates component and its children (if any)
     */
    public static void iterate(final IComponent component, final ComponentVisitor visitor) {
        iterateImpl(component, visitor);
    }

    private static boolean iterateImpl(final IComponent component, final ComponentVisitor visitor) {
        final boolean shouldContinue;
        try {
            shouldContinue = visitor.visit(component);
        } catch (ProcessCanceledException ex) {
            return false;
        }
        if (!shouldContinue) {
            return false;
        }

        if (!(component instanceof IContainer)) {
            return true;
        }

        final IContainer container = (IContainer) component;

        for (int i = 0; i < container.getComponentCount(); i++) {
            final IComponent c = container.getComponent(i);
            if (!iterateImpl(c, visitor)) {
                return false;
            }
        }

        return true;
    }

    public static Set<String> collectUsedBundleNames(final IRootContainer rootContainer) {
        final Set<String> bundleNames = new HashSet<String>();
        iterateStringDescriptors(rootContainer, new StringDescriptorVisitor<IComponent>() {
            public boolean visit(final IComponent component, final StringDescriptor descriptor) {
                if (descriptor.getBundleName() != null && !bundleNames.contains(descriptor.getBundleName())) {
                    bundleNames.add(descriptor.getBundleName());
                }
                return true;
            }
        });
        return bundleNames;
    }

    public static Locale[] collectUsedLocales(final Module module, final IRootContainer rootContainer) {
        final Set<Locale> locales = new HashSet<Locale>();
        final PropertiesReferenceManager propManager = PropertiesReferenceManager.getInstance(module.getProject());
        for (String bundleName : collectUsedBundleNames(rootContainer)) {
            List<PropertiesFile> propFiles = propManager.findPropertiesFiles(module, bundleName.replace('/', '.'));
            for (PropertiesFile propFile : propFiles) {
                locales.add(propFile.getLocale());
            }
        }
        return locales.toArray(new Locale[locales.size()]);
    }

    public static void deleteRowOrColumn(final GuiEditor editor, final RadContainer container,
            final int[] cellsToDelete, final boolean isRow) {
        Arrays.sort(cellsToDelete);
        final int[] cells = ArrayUtil.reverseArray(cellsToDelete);
        if (!editor.ensureEditable()) {
            return;
        }

        Runnable runnable = new Runnable() {
            public void run() {
                if (!GridChangeUtil.canDeleteCells(container, cells, isRow)) {
                    Set<RadComponent> componentsInColumn = new HashSet<RadComponent>();
                    for (RadComponent component : container.getComponents()) {
                        GridConstraints c = component.getConstraints();
                        for (int cell : cells) {
                            if (c.contains(isRow, cell)) {
                                componentsInColumn.add(component);
                                break;
                            }
                        }
                    }

                    if (componentsInColumn.size() > 0) {
                        String message = isRow
                                ? UIDesignerBundle.message("delete.row.nonempty", componentsInColumn.size(),
                                        cells.length)
                                : UIDesignerBundle.message("delete.column.nonempty", componentsInColumn.size(),
                                        cells.length);

                        final int rc = Messages.showYesNoDialog(editor, message,
                                isRow ? UIDesignerBundle.message("delete.row.title")
                                        : UIDesignerBundle.message("delete.column.title"),
                                Messages.getQuestionIcon());
                        if (rc != Messages.YES) {
                            return;
                        }

                        deleteComponents(componentsInColumn, false);
                    }
                }

                for (int cell : cells) {
                    container.getGridLayoutManager().deleteGridCells(container, cell, isRow);
                }
                editor.refreshAndSave(true);
            }
        };
        CommandProcessor.getInstance().executeCommand(editor.getProject(), runnable,
                isRow ? UIDesignerBundle.message("command.delete.row")
                        : UIDesignerBundle.message("command.delete.column"),
                null);
    }

    /**
     * @param rootContainer
     * @return id
     */
    public static String generateId(final RadRootContainer rootContainer) {
        while (true) {
            final String id = Integer.toString((int) (Math.random() * 1024 * 1024), 16);
            if (findComponent(rootContainer, id) == null) {
                return id;
            }
        }
    }

    /**
     * @return {@link com.intellij.uiDesigner.designSurface.GuiEditor} from the context. Can be <code>null</code>.
     */
    @Nullable
    public static GuiEditor getEditorFromContext(@NotNull final DataContext context) {
        final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
        if (editor instanceof UIFormEditor) {
            return ((UIFormEditor) editor).getEditor();
        } else {
            return GuiEditor.DATA_KEY.getData(context);
        }
    }

    @Nullable
    public static GuiEditor getActiveEditor(final DataContext context) {
        Project project = CommonDataKeys.PROJECT.getData(context);
        if (project == null) {
            return null;
        }
        final DesignerToolWindowManager toolWindowManager = DesignerToolWindowManager.getInstance(project);
        if (toolWindowManager == null) {
            return null;
        }
        return toolWindowManager.getActiveFormEditor();
    }

    /**
     * @param componentToAssignBinding
     * @param binding
     * @param component                topmost container where to find duplicate binding. In most cases
     *                                 it should be {@link com.intellij.uiDesigner.designSurface.GuiEditor#getRootContainer()}
     */
    public static boolean isBindingUnique(final IComponent componentToAssignBinding, final String binding,
            final IComponent component) {
        return findComponentWithBinding(component, binding, componentToAssignBinding) == null;
    }

    @Nullable
    public static String buildResourceName(final PsiFile file) {
        PsiDirectory directory = file.getContainingDirectory();
        if (directory != null) {
            PsiPackage pkg = JavaDirectoryService.getInstance().getPackage(directory);
            String packageName = pkg != null ? pkg.getQualifiedName() : "";
            if (packageName.length() == 0) {
                return file.getName();
            }
            return packageName.replace('.', '/') + '/' + file.getName();
        }
        return null;
    }

    @Nullable
    public static RadContainer getSelectionParent(final List<RadComponent> selection) {
        RadContainer parent = null;
        for (RadComponent c : selection) {
            if (parent == null) {
                parent = c.getParent();
            } else if (parent != c.getParent()) {
                parent = null;
                break;
            }
        }
        return parent;
    }

    public static Rectangle getSelectionBounds(List<RadComponent> selection) {
        int minRow = Integer.MAX_VALUE;
        int minCol = Integer.MAX_VALUE;
        int maxRow = 0;
        int maxCol = 0;

        for (RadComponent c : selection) {
            minRow = Math.min(minRow, c.getConstraints().getRow());
            minCol = Math.min(minCol, c.getConstraints().getColumn());
            maxRow = Math.max(maxRow, c.getConstraints().getRow() + c.getConstraints().getRowSpan());
            maxCol = Math.max(maxCol, c.getConstraints().getColumn() + c.getConstraints().getColSpan());
        }
        return new Rectangle(minCol, minRow, maxCol - minCol, maxRow - minRow);
    }

    public static boolean isComponentSwitchedInView(@NotNull RadComponent component) {
        while (component.getParent() != null) {
            if (!component.getParent().getLayoutManager().isSwitchedToChild(component.getParent(), component)) {
                return false;
            }
            component = component.getParent();
        }
        return true;
    }

    /**
     * Selects the component and ensures that the tabbed panes containing the component are
     * switched to the correct tab.
     *
     * @param editor
     * @param component the component to select. @return true if the component is enclosed in at least one tabbed pane, false otherwise.
     */
    public static boolean selectComponent(final GuiEditor editor, @NotNull final RadComponent component) {
        boolean hasTab = false;
        RadComponent parent = component;
        while (parent.getParent() != null) {
            if (parent.getParent().getLayoutManager().switchContainerToChild(parent.getParent(), parent)) {
                hasTab = true;
            }
            parent = parent.getParent();
        }
        component.setSelected(true);
        editor.setSelectionLead(component);
        return hasTab;
    }

    public static void selectSingleComponent(final GuiEditor editor, final RadComponent component) {
        final RadContainer root = (RadContainer) getRoot(component);
        if (root == null) {
            return;
        }

        ComponentTreeBuilder builder = DesignerToolWindowManager.getInstance(editor).getComponentTreeBuilder();
        // this can return null if the click to select the control also requested to grab the focus -
        // the component tree will be instantiated after the event has been processed completely
        if (builder != null) {
            builder.beginUpdateSelection();
        }
        try {
            clearSelection(root);
            selectComponent(editor, component);
            editor.setSelectionAnchor(component);
            editor.scrollComponentInView(component);
        } finally {
            if (builder != null) {
                builder.endUpdateSelection();
            }
        }
    }

    public static void selectComponents(final GuiEditor editor, List<RadComponent> components) {
        if (components.size() > 0) {
            RadComponent component = components.get(0);
            ComponentTreeBuilder builder = DesignerToolWindowManager.getInstance(editor).getComponentTreeBuilder();
            if (builder == null) {
                // race condition when handling event?
                return;
            }
            builder.beginUpdateSelection();
            try {
                clearSelection((RadContainer) getRoot(component));
                for (RadComponent aComponent : components) {
                    selectComponent(editor, aComponent);
                }
            } finally {
                builder.endUpdateSelection();
            }
        }
    }

    public static boolean isDropOnChild(final DraggedComponentList draggedComponentList,
            final ComponentDropLocation location) {
        if (location.getContainer() == null) {
            return false;
        }

        for (RadComponent component : draggedComponentList.getComponents()) {
            if (isChild(location.getContainer(), component)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isChild(RadContainer maybeChild, RadComponent maybeParent) {
        while (maybeChild != null) {
            if (maybeParent == maybeChild) {
                return true;
            }
            maybeChild = maybeChild.getParent();
        }
        return false;
    }

    public static PsiMethod findCreateComponentsMethod(final PsiClass aClass) {
        PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();
        PsiMethod method;
        try {
            method = factory.createMethodFromText(
                    "void " + AsmCodeGenerator.CREATE_COMPONENTS_METHOD_NAME + "() {}", aClass);
        } catch (IncorrectOperationException e) {
            throw new RuntimeException(e);
        }
        return aClass.findMethodBySignature(method, true);
    }

    public static Object getNextSaveUndoGroupId(final Project project) {
        final GuiEditor guiEditor = DesignerToolWindowManager.getInstance(project).getActiveFormEditor();
        return guiEditor == null ? null : guiEditor.getNextSaveGroupId();
    }

    public static int adjustForGap(final RadContainer container, final int cellIndex, final boolean isRow,
            final int delta) {
        if (container.getGridLayoutManager().isGapCell(container, isRow, cellIndex)) {
            return cellIndex + delta;
        }
        return cellIndex;
    }

    public static int prevRow(final RadContainer container, final int row) {
        return adjustForGap(container, row - 1, true, -1);
    }

    public static int nextRow(final RadContainer container, final int row) {
        return adjustForGap(container, row + 1, true, 1);
    }

    public static int prevCol(final RadContainer container, final int col) {
        return adjustForGap(container, col - 1, false, -1);
    }

    public static int nextCol(final RadContainer container, final int col) {
        return adjustForGap(container, col + 1, false, 1);
    }

    @Nullable
    public static IButtonGroup findGroupForComponent(final IRootContainer radRootContainer,
            @NotNull final IComponent component) {
        for (IButtonGroup group : radRootContainer.getButtonGroups()) {
            for (String id : group.getComponentIds()) {
                if (component.getId().equals(id)) {
                    return group;
                }
            }
        }
        return null;
    }

    public static void remapToActionTargets(final List<RadComponent> selection) {
        for (int i = 0; i < selection.size(); i++) {
            final RadComponent c = selection.get(i);
            if (c.getParent() != null) {
                selection.set(i, c.getParent().getActionTargetComponent(c));
            }
        }
    }

    public static void showPopupUnderComponent(final JBPopup popup, final RadComponent selectedComponent) {
        // popup.showUnderneathOf() doesn't work on invisible components
        Rectangle rc = selectedComponent.getBounds();
        Point pnt = new Point(rc.x, rc.y + rc.height);
        popup.show(new RelativePoint(selectedComponent.getDelegee().getParent(), pnt));
    }

    public interface StringDescriptorVisitor<T extends IComponent> {
        boolean visit(T component, StringDescriptor descriptor);
    }

    public static void iterateStringDescriptors(final IComponent component,
            final StringDescriptorVisitor<IComponent> visitor) {
        iterate(component, new ComponentVisitor<IComponent>() {

            public boolean visit(final IComponent component) {
                for (IProperty prop : component.getModifiedProperties()) {
                    Object value = prop.getPropertyValue(component);
                    if (value instanceof StringDescriptor) {
                        if (!visitor.visit(component, (StringDescriptor) value)) {
                            return false;
                        }
                    }
                }
                if (component.getParentContainer() instanceof ITabbedPane) {
                    StringDescriptor tabTitle = ((ITabbedPane) component.getParentContainer())
                            .getTabProperty(component, ITabbedPane.TAB_TITLE_PROPERTY);
                    if (tabTitle != null && !visitor.visit(component, tabTitle)) {
                        return false;
                    }
                    StringDescriptor tabToolTip = ((ITabbedPane) component.getParentContainer())
                            .getTabProperty(component, ITabbedPane.TAB_TOOLTIP_PROPERTY);
                    if (tabToolTip != null && !visitor.visit(component, tabToolTip)) {
                        return false;
                    }
                }
                if (component instanceof IContainer) {
                    final StringDescriptor borderTitle = ((IContainer) component).getBorderTitle();
                    if (borderTitle != null && !visitor.visit(component, borderTitle)) {
                        return false;
                    }
                }
                return true;
            }
        });
    }

    public static void clearSelection(@NotNull final RadContainer container) {
        container.setSelected(false);

        for (int i = 0; i < container.getComponentCount(); i++) {
            final RadComponent c = container.getComponent(i);
            if (c instanceof RadContainer) {
                clearSelection((RadContainer) c);
            } else {
                c.setSelected(false);
            }
        }
    }

    /**
     * Finds component with the specified <code>id</code> starting from the
     * <code>container</code>. The method goes recursively through the hierarchy
     * of components. Note, that if <code>container</code> itself has <code>id</code>
     * then the method immediately retuns it.
     *
     * @return the found component.
     */
    @Nullable
    public static IComponent findComponent(@NotNull final IComponent component, @NotNull final String id) {
        if (id.equals(component.getId())) {
            return component;
        }
        if (!(component instanceof IContainer)) {
            return null;
        }

        final IContainer uiContainer = (IContainer) component;
        for (int i = 0; i < uiContainer.getComponentCount(); i++) {
            final IComponent found = findComponent(uiContainer.getComponent(i), id);
            if (found != null) {
                return found;
            }
        }
        return null;
    }

    @Nullable
    public static PsiClass findClassToBind(@NotNull final Module module, @NotNull final String classToBindName) {
        return JavaPsiFacade.getInstance(module.getProject()).findClass(classToBindName.replace('$', '.'),
                module.getModuleWithDependenciesScope());
    }

    public interface ComponentVisitor<Type extends IComponent> {
        /**
         * @return true if iteration should continue
         */
        boolean visit(Type component);
    }
}