com.intellij.uiDesigner.radComponents.RadFormLayoutManager.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.uiDesigner.radComponents.RadFormLayoutManager.java

Source

/*
 * Copyright 2000-2012 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.radComponents;

import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.uiDesigner.GridChangeUtil;
import com.intellij.uiDesigner.UIDesignerBundle;
import com.intellij.uiDesigner.UIFormXmlConstants;
import com.intellij.uiDesigner.XmlWriter;
import com.intellij.uiDesigner.actions.*;
import com.intellij.uiDesigner.compiler.FormLayoutUtils;
import com.intellij.uiDesigner.compiler.Utils;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.designSurface.*;
import com.intellij.uiDesigner.lw.FormLayoutSerializer;
import com.intellij.uiDesigner.propertyInspector.Property;
import com.intellij.uiDesigner.propertyInspector.properties.AbstractInsetsProperty;
import com.intellij.uiDesigner.propertyInspector.properties.AlignPropertyProvider;
import com.intellij.uiDesigner.propertyInspector.properties.HorzAlignProperty;
import com.intellij.uiDesigner.propertyInspector.properties.VertAlignProperty;
import com.intellij.uiDesigner.snapShooter.SnapshotContext;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.PlatformColors;
import com.jgoodies.forms.layout.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yole
 */
public class RadFormLayoutManager extends RadAbstractGridLayoutManager implements AlignPropertyProvider {
    private FormLayoutColumnProperties myPropertiesPanel;

    @NonNls
    private static final String ENCODED_FORMSPEC_GROW = "d:grow";
    private static final Size DEFAULT_NOGROW_SIZE = new BoundedSize(Sizes.DEFAULT,
            new ConstantSize(4, ConstantSize.PIXEL), null);

    @Nullable
    public String getName() {
        return UIFormXmlConstants.LAYOUT_FORM;
    }

    @Override
    @Nullable
    public LayoutManager createLayout() {
        return new FormLayout(ENCODED_FORMSPEC_GROW, ENCODED_FORMSPEC_GROW);
    }

    @Override
    protected void changeLayoutFromGrid(final RadContainer container, final List<RadComponent> contents,
            final List<Boolean> canRowsGrow, final List<Boolean> canColumnsGrow) {
        int rowCount = canRowsGrow.size();
        int columnCount = canColumnsGrow.size();
        int rowCountWithGaps = (rowCount == 0) ? 0 : rowCount * 2 - 1;
        int columnCountWithGaps = (columnCount == 0) ? 0 : columnCount * 2 - 1;
        RowSpec[] rowSpecs = new RowSpec[rowCountWithGaps];
        ColumnSpec[] colSpecs = new ColumnSpec[columnCountWithGaps];

        for (int i = 0; i < rowCount; i++) {
            rowSpecs[i * 2] = canRowsGrow.get(i).booleanValue() ? RowSpec.decode(ENCODED_FORMSPEC_GROW)
                    : new RowSpec(DEFAULT_NOGROW_SIZE);
            if (i * 2 + 1 < rowSpecs.length) {
                rowSpecs[i * 2 + 1] = FormSpecs.RELATED_GAP_ROWSPEC;
            }
        }
        for (int i = 0; i < columnCount; i++) {
            colSpecs[i * 2] = canColumnsGrow.get(i).booleanValue() ? ColumnSpec.decode(ENCODED_FORMSPEC_GROW)
                    : new ColumnSpec(DEFAULT_NOGROW_SIZE);
            if (i * 2 + 1 < colSpecs.length) {
                colSpecs[i * 2 + 1] = FormSpecs.RELATED_GAP_COLSPEC;
            }
        }

        container.setLayoutManager(this, new FormLayout(colSpecs, rowSpecs));
    }

    @Override
    protected void changeLayoutFromIndexed(final RadContainer container, final List<RadComponent> components) {
        int maxSizePolicy = 0;
        for (RadComponent c : components) {
            maxSizePolicy = Math.max(maxSizePolicy, c.getConstraints().getHSizePolicy());
        }
        ColumnSpec[] colSpecs = new ColumnSpec[components.size() * 2 - 1];
        for (int i = 0; i < components.size(); i++) {
            colSpecs[i * 2] = components.get(i).getConstraints().getHSizePolicy() == maxSizePolicy
                    ? ColumnSpec.decode(ENCODED_FORMSPEC_GROW)
                    : FormSpecs.DEFAULT_COLSPEC;
            if (i * 2 + 1 < colSpecs.length) {
                colSpecs[i * 2 + 1] = FormSpecs.RELATED_GAP_COLSPEC;
            }
        }
        container.setLayoutManager(this, new FormLayout(colSpecs, new RowSpec[] { FormSpecs.DEFAULT_ROWSPEC }));
    }

    @Override
    public void writeLayout(final XmlWriter writer, final RadContainer radContainer) {
        FormLayout layout = (FormLayout) radContainer.getLayout();
        for (int i = 1; i <= layout.getRowCount(); i++) {
            RowSpec rowSpec = layout.getRowSpec(i);
            writer.startElement(UIFormXmlConstants.ELEMENT_ROWSPEC);
            try {
                writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_VALUE, FormLayoutUtils.getEncodedSpec(rowSpec));
            } finally {
                writer.endElement();
            }
        }
        for (int i = 1; i <= layout.getColumnCount(); i++) {
            ColumnSpec columnSpec = layout.getColumnSpec(i);
            writer.startElement(UIFormXmlConstants.ELEMENT_COLSPEC);
            try {
                writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_VALUE, FormLayoutUtils.getEncodedSpec(columnSpec));
            } finally {
                writer.endElement();
            }
        }
        writeGroups(writer, UIFormXmlConstants.ELEMENT_ROWGROUP, layout.getRowGroups());
        writeGroups(writer, UIFormXmlConstants.ELEMENT_COLGROUP, layout.getColumnGroups());
    }

    private static void writeGroups(final XmlWriter writer, final String elementName, final int[][] groups) {
        for (int[] group : groups) {
            writer.startElement(elementName);
            try {
                for (int member : group) {
                    writer.startElement(UIFormXmlConstants.ELEMENT_MEMBER);
                    writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_INDEX, member);
                    writer.endElement();
                }
            } finally {
                writer.endElement();
            }
        }
    }

    @Override
    public void addComponentToContainer(final RadContainer container, final RadComponent component,
            final int index) {
        super.addComponentToContainer(container, component, index);
        final CellConstraints cc = gridToCellConstraints(component);
        if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
            CellConstraints customCellConstraints = (CellConstraints) component.getCustomLayoutConstraints();
            cc.insets = customCellConstraints.insets;
        }
        component.setCustomLayoutConstraints(cc);
        container.getDelegee().add(component.getDelegee(), cc, index);
    }

    private static CellConstraints gridToCellConstraints(final RadComponent component) {
        GridConstraints gc = component.getConstraints();
        CellConstraints.Alignment hAlign = ((gc.getHSizePolicy() & GridConstraints.SIZEPOLICY_WANT_GROW) != 0)
                ? CellConstraints.FILL
                : CellConstraints.DEFAULT;
        CellConstraints.Alignment vAlign = ((gc.getVSizePolicy() & GridConstraints.SIZEPOLICY_WANT_GROW) != 0)
                ? CellConstraints.FILL
                : CellConstraints.DEFAULT;
        if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
            CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
            hAlign = cc.hAlign;
            vAlign = cc.vAlign;
        }
        return new CellConstraints(gc.getColumn() + 1, gc.getRow() + 1, gc.getColSpan(), gc.getRowSpan(), hAlign,
                vAlign);
    }

    @Override
    public void writeChildConstraints(final XmlWriter writer, final RadComponent child) {
        writeGridConstraints(writer, child);
        if (child.getCustomLayoutConstraints() instanceof CellConstraints) {
            CellConstraints cc = (CellConstraints) child.getCustomLayoutConstraints();
            writer.startElement(UIFormXmlConstants.ELEMENT_FORMS);
            try {
                if (!cc.insets.equals(new Insets(0, 0, 0, 0))) {
                    writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TOP, cc.insets.top);
                    writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_LEFT, cc.insets.left);
                    writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_BOTTOM, cc.insets.bottom);
                    writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_RIGHT, cc.insets.right);
                }
                if (cc.hAlign != CellConstraints.DEFAULT) {
                    writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_DEFAULTALIGN_HORZ, false);
                }
                if (cc.vAlign != CellConstraints.DEFAULT) {
                    writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_DEFAULTALIGN_VERT, false);
                }
            } finally {
                writer.endElement();
            }
        }
    }

    private static FormLayout getFormLayout(final RadContainer container) {
        return (FormLayout) container.getLayout();
    }

    @Override
    public int getGridRowCount(RadContainer container) {
        return getFormLayout(container).getRowCount();
    }

    @Override
    public int getGridColumnCount(RadContainer container) {
        return getFormLayout(container).getColumnCount();
    }

    @Override
    public int[] getGridCellCoords(RadContainer container, boolean isRow) {
        final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
        int[] origins = isRow ? layoutInfo.rowOrigins : layoutInfo.columnOrigins;
        int[] result = new int[origins.length - 1];
        System.arraycopy(origins, 0, result, 0, result.length);
        return result;
    }

    @Override
    public int[] getGridCellSizes(RadContainer container, boolean isRow) {
        final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
        int[] origins = isRow ? layoutInfo.rowOrigins : layoutInfo.columnOrigins;
        int[] result = new int[origins.length - 1];
        for (int i = 0; i < result.length; i++) {
            result[i] = origins[i + 1] - origins[i];
        }
        return result;
    }

    @Override
    public int[] getHorizontalGridLines(RadContainer container) {
        final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
        return layoutInfo.rowOrigins;
    }

    @Override
    public int[] getVerticalGridLines(RadContainer container) {
        final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
        return layoutInfo.columnOrigins;
    }

    @Override
    public int getGridRowAt(RadContainer container, int y) {
        final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
        return findCell(layoutInfo.rowOrigins, y);
    }

    @Override
    public int getGridColumnAt(RadContainer container, int x) {
        final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
        return findCell(layoutInfo.columnOrigins, x);
    }

    private static int findCell(final int[] origins, final int coord) {
        for (int i = 0; i < origins.length - 1; i++) {
            if (coord >= origins[i] && coord < origins[i + 1])
                return i;
        }
        return -1;
    }

    @NotNull
    @Override
    public ComponentDropLocation getDropLocation(@NotNull RadContainer container, @Nullable final Point location) {
        FormLayout formLayout = getFormLayout(container);
        if (formLayout.getRowCount() == 0 || formLayout.getColumnCount() == 0) {
            if (location != null) {
                Rectangle rc = new Rectangle(new Point(), container.getDelegee().getSize());
                return new FormFirstComponentInsertLocation(container, location, rc);
            }
        }
        final FormLayout.LayoutInfo layoutInfo = formLayout.getLayoutInfo(container.getDelegee());
        if (location != null && location.x > layoutInfo.getWidth()) {
            int row = findCell(layoutInfo.rowOrigins, location.y);
            if (row == -1) {
                return NoDropLocation.INSTANCE;
            }
            return new GridInsertLocation(container, row, getGridColumnCount(container) - 1,
                    GridInsertMode.ColumnAfter);
        }
        if (location != null && location.y > layoutInfo.getHeight()) {
            int column = findCell(layoutInfo.columnOrigins, location.x);
            if (column == -1) {
                return NoDropLocation.INSTANCE;
            }
            return new GridInsertLocation(container, getGridRowCount(container) - 1, column,
                    GridInsertMode.RowAfter);
        }

        if (container.getGridRowCount() == 1 && container.getGridColumnCount() == 1
                && getComponentAtGrid(container, 0, 0) == null) {
            final Rectangle rc = getGridCellRangeRect(container, 0, 0, 0, 0);
            if (location == null) {
                return new FormFirstComponentInsertLocation(container, rc, 0, 0);
            }
            return new FormFirstComponentInsertLocation(container, location, rc);
        }

        return super.getDropLocation(container, location);
    }

    @Override
    public CustomPropertiesPanel getRowColumnPropertiesPanel(RadContainer container, boolean isRow,
            int[] selectedIndices) {
        if (myPropertiesPanel == null) {
            myPropertiesPanel = new FormLayoutColumnProperties();
        }
        myPropertiesPanel.showProperties(container, isRow, selectedIndices);
        return myPropertiesPanel;
    }

    @Override
    public ActionGroup getCaptionActions() {
        DefaultActionGroup group = new DefaultActionGroup();
        group.add(new InsertBeforeAction());
        group.add(new InsertAfterAction());
        group.add(new SplitAction());
        group.add(new DeleteAction());
        group.add(new GroupRowsColumnsAction());
        group.add(new UngroupRowsColumnsAction());
        return group;
    }

    @Override
    public boolean canCellGrow(RadContainer container, boolean isRow, int index) {
        FormLayout layout = (FormLayout) container.getLayout();
        FormSpec spec = isRow ? layout.getRowSpec(index + 1) : layout.getColumnSpec(index + 1);
        return spec.getResizeWeight() > 0.01d;
    }

    @Override
    public void setChildDragging(RadComponent child, boolean dragging) {
        // do nothing here - otherwise the layout will jump around
    }

    @Override
    public void paintCaptionDecoration(final RadContainer container, final boolean isRow, final int index,
            final Graphics2D g2d, final Rectangle rc) {
        // don't paint gap rows/columns with red background
        if (isGapCell(container, isRow, index)) {
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillRect(rc.x, rc.y, rc.width, rc.height);
        }

        if (canCellGrow(container, isRow, index)) {
            drawGrowMarker(isRow, g2d, rc);
        }

        FormLayout layout = (FormLayout) container.getLayout();
        int[][] groups = isRow ? layout.getRowGroups() : layout.getColumnGroups();
        //noinspection MultipleVariablesInDeclaration
        boolean haveTopLeft = false, haveTopRight = false, haveTopLine = false;
        //noinspection MultipleVariablesInDeclaration
        boolean haveBottomLeft = false, haveBottomRight = false, haveBottomLine = false;
        boolean inGroup = false;
        for (int i = 0; i < groups.length; i++) {
            int minMember = Integer.MAX_VALUE;
            int maxMember = -1;
            for (int member : groups[i]) {
                minMember = Math.min(member - 1, minMember);
                maxMember = Math.max(member - 1, maxMember);
                inGroup = inGroup || (member - 1 == index);
            }
            if (minMember <= index && index <= maxMember) {
                if (i % 2 == 0) {
                    haveTopLeft = haveTopLeft || index > minMember;
                    haveTopRight = haveTopRight || index < maxMember;
                    haveTopLine = haveTopLine || inGroup;
                } else {
                    haveBottomLeft = haveBottomLeft || index > minMember;
                    haveBottomRight = haveBottomRight || index < maxMember;
                    haveBottomLine = haveBottomLine || inGroup;
                }
            }
        }

        g2d.setColor(PlatformColors.BLUE);
        drawGroupLine(rc, isRow, g2d, true, haveTopLeft, haveTopRight, haveTopLine);
        drawGroupLine(rc, isRow, g2d, false, haveBottomLeft, haveBottomRight, haveBottomLine);
    }

    private static void drawGroupLine(final Rectangle rc, final boolean isRow, final Graphics2D g2d, boolean isTop,
            final boolean haveLeft, final boolean haveRight, final boolean haveLine) {

        int maxX = (int) rc.getMaxX();
        int maxY = (int) rc.getMaxY();
        Point linePos = isTop ? new Point(rc.x + 1, rc.y + 1) : new Point(rc.x + 3, rc.y + 3);
        Point markerPos = new Point(rc.x + 6, rc.y + 6);

        int midX = (int) rc.getCenterX();
        int midY = (int) rc.getCenterY();
        if (haveLine) {
            if (isRow) {
                g2d.drawLine(linePos.x, midY, markerPos.x, midY);
            } else {
                g2d.drawLine(midX, linePos.y, midX, markerPos.y);
            }
        }
        if (haveLeft) {
            if (isRow) {
                g2d.drawLine(linePos.x, rc.y, linePos.x, midY);
            } else {
                g2d.drawLine(rc.x, linePos.y, midX, linePos.y);
            }
        }
        if (haveRight) {
            if (isRow) {
                g2d.drawLine(linePos.x, midY, linePos.x, maxY);
            } else {
                g2d.drawLine(midX, linePos.y, maxX, linePos.y);
            }
        }
    }

    @Override
    public Property[] getContainerProperties(final Project project) {
        return Property.EMPTY_ARRAY;
    }

    @Override
    public Property[] getComponentProperties(final Project project, final RadComponent component) {
        return new Property[] { HorzAlignProperty.getInstance(project), VertAlignProperty.getInstance(project),
                new ComponentInsetsProperty() };
    }

    @Override
    public int insertGridCells(final RadContainer grid, final int cellIndex, final boolean isRow,
            final boolean isBefore, final boolean grow) {
        FormSpec formSpec;
        if (isRow) {
            formSpec = grow ? RowSpec.decode(ENCODED_FORMSPEC_GROW) : new RowSpec(DEFAULT_NOGROW_SIZE);
        } else {
            formSpec = grow ? ColumnSpec.decode(ENCODED_FORMSPEC_GROW) : new ColumnSpec(DEFAULT_NOGROW_SIZE);
        }
        insertGridCells(grid, cellIndex, isRow, isBefore, formSpec);
        return getGridCellCount(grid, isRow) == 1 ? 1 : 2;
    }

    @Override
    public void copyGridCells(RadContainer source, final RadContainer destination, final boolean isRow,
            int cellIndex, int cellCount, int targetIndex) {
        FormLayout sourceLayout = getFormLayout(source);
        FormLayout destinationLayout = getFormLayout(destination);
        if (isRow) {
            insertOrAppendRow(destinationLayout, targetIndex + 1, FormSpecs.RELATED_GAP_ROWSPEC);
        } else {
            insertOrAppendColumn(destinationLayout, targetIndex + 1, FormSpecs.RELATED_GAP_COLSPEC);
        }
        targetIndex++;
        if (targetIndex < cellIndex)
            cellIndex++;
        copyFormSpecs(sourceLayout, destinationLayout, isRow, cellIndex, cellCount, targetIndex);
        updateGridConstraintsFromCellConstraints(destination);
    }

    private static void copyFormSpecs(final FormLayout sourceLayout, final FormLayout destinationLayout,
            final boolean isRow, int cellIndex, int cellCount, int targetIndex) {
        for (int i = 0; i < cellCount; i++) {
            if (isRow) {
                RowSpec rowSpec = sourceLayout.getRowSpec(cellIndex + 1);
                insertOrAppendRow(destinationLayout, targetIndex + 1, rowSpec);
            } else {
                ColumnSpec colSpec = sourceLayout.getColumnSpec(cellIndex + 1);
                insertOrAppendColumn(destinationLayout, targetIndex + 1, colSpec);
            }
            cellIndex += (targetIndex < cellIndex && sourceLayout == destinationLayout) ? 2 : 1;
            targetIndex++;
        }
    }

    @Override
    public void copyGridSection(final RadContainer source, final RadContainer destination, final Rectangle rc) {
        final FormLayout destinationLayout = new FormLayout();
        destination.setLayout(destinationLayout);
        copyFormSpecs(getFormLayout(source), destinationLayout, true, rc.y, rc.height, 0);
        copyFormSpecs(getFormLayout(source), destinationLayout, false, rc.x, rc.width, 0);
    }

    @Override
    public int getGapCellCount() {
        return 1;
    }

    @Override
    public int getGapCellSize(final RadContainer container, boolean isRow) {
        Size size = isRow ? FormSpecs.RELATED_GAP_ROWSPEC.getSize() : FormSpecs.RELATED_GAP_COLSPEC.getSize();
        if (size instanceof ConstantSize) {
            return ((ConstantSize) size).getPixelSize(container.getDelegee());
        }
        return 0;
    }

    @Override
    public boolean isGapCell(RadContainer grid, boolean isRow, int cellIndex) {
        if (cellIndex < 0 || cellIndex >= getGridCellCount(grid, isRow)) {
            return false;
        }
        if (cellIndex % 2 == 1) {
            final GridChangeUtil.CellStatus status = GridChangeUtil.canDeleteCell(grid, cellIndex, isRow);
            if (status == GridChangeUtil.CellStatus.Empty || status == GridChangeUtil.CellStatus.Redundant) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int getCellIndexBase() {
        return 1;
    }

    /**
     * @return index where new column or row was actually inserted (0-based)
     */
    private int insertGridCells(RadContainer grid, int cellIndex, boolean isRow, boolean isBefore,
            FormSpec formSpec) {
        FormLayout formLayout = (FormLayout) grid.getLayout();
        int index = isBefore ? cellIndex + 1 : cellIndex + 2;
        if (isRow) {
            if (getGridCellCount(grid, true) > 0) {
                insertOrAppendRow(formLayout, index, FormSpecs.RELATED_GAP_ROWSPEC);
                if (!isBefore)
                    index++;
            }
            insertOrAppendRow(formLayout, index, (RowSpec) formSpec);
        } else {
            if (getGridCellCount(grid, false) > 0) {
                insertOrAppendColumn(formLayout, index, FormSpecs.RELATED_GAP_COLSPEC);
                if (!isBefore)
                    index++;
            }
            insertOrAppendColumn(formLayout, index, (ColumnSpec) formSpec);
        }
        updateGridConstraintsFromCellConstraints(grid);
        return index - 1;
    }

    private static void insertOrAppendRow(final FormLayout formLayout, final int index, final RowSpec rowSpec) {
        if (index == formLayout.getRowCount() + 1) {
            formLayout.appendRow(rowSpec);
        } else {
            formLayout.insertRow(index, rowSpec);
        }
    }

    private static void insertOrAppendColumn(final FormLayout formLayout, final int index,
            final ColumnSpec columnSpec) {
        if (index == formLayout.getColumnCount() + 1) {
            formLayout.appendColumn(columnSpec);
        } else {
            formLayout.insertColumn(index, columnSpec);
        }
    }

    @Override
    public int deleteGridCells(final RadContainer grid, final int cellIndex, final boolean isRow) {
        int result = 1;
        FormLayout formLayout = (FormLayout) grid.getLayout();
        adjustDeletedCellOrigins(grid, cellIndex, isRow);
        if (isRow) {
            int[][] groupIndices = formLayout.getRowGroups();
            groupIndices = removeDeletedCell(groupIndices, cellIndex + 1);
            formLayout.setRowGroups(groupIndices);
            formLayout.removeRow(cellIndex + 1);
            updateGridConstraintsFromCellConstraints(grid);
            if (formLayout.getRowCount() > 0 && formLayout.getRowCount() % 2 == 0) {
                int gapRowIndex = (cellIndex >= grid.getGridRowCount()) ? cellIndex - 1 : cellIndex;
                if (GridChangeUtil.isRowEmpty(grid, gapRowIndex)) {
                    formLayout.removeRow(gapRowIndex + 1);
                    updateGridConstraintsFromCellConstraints(grid);
                    result++;
                }
            }
        } else {
            int[][] groupIndices = formLayout.getColumnGroups();
            groupIndices = removeDeletedCell(groupIndices, cellIndex + 1);
            formLayout.setColumnGroups(groupIndices);
            formLayout.removeColumn(cellIndex + 1);
            updateGridConstraintsFromCellConstraints(grid);
            if (formLayout.getColumnCount() > 0 && formLayout.getColumnCount() % 2 == 0) {
                int gapColumnIndex = (cellIndex >= grid.getGridColumnCount()) ? cellIndex - 1 : cellIndex;
                if (GridChangeUtil.isColumnEmpty(grid, gapColumnIndex)) {
                    formLayout.removeColumn(gapColumnIndex + 1);
                    updateGridConstraintsFromCellConstraints(grid);
                    result++;
                }
            }
        }
        return result;
    }

    private void adjustDeletedCellOrigins(final RadContainer grid, final int cellIndex, final boolean isRow) {
        int gapCellDelta = isGapCell(grid, isRow, cellIndex + 1) ? 2 : 1;
        for (RadComponent component : grid.getComponents()) {
            // ensure that we don't have component origins in the deleted cells
            final GridConstraints gc = component.getConstraints();
            if (gc.getCell(isRow) == cellIndex) {
                final int span = gc.getSpan(isRow);
                if (span > gapCellDelta) {
                    gc.setCell(isRow, cellIndex + gapCellDelta);
                    gc.setSpan(isRow, span - gapCellDelta);
                    updateConstraints(component);
                } else {
                    throw new IllegalArgumentException(
                            "Attempt to delete grid row/column which contains origins of 1-span components");
                }
            }
        }
    }

    private static int[][] removeDeletedCell(final int[][] groupIndices, final int deletedIndex) {
        for (int i = 0; i < groupIndices.length; i++) {
            for (int j = 0; j < groupIndices[i].length; j++) {
                if (groupIndices[i][j] == deletedIndex) {
                    int[][] newIndices;
                    if (groupIndices[i].length <= 2) {
                        // deleted cell is contained in a group with 1 or 2 cells => delete entire group
                        newIndices = new int[groupIndices.length - 1][];
                        for (int newI = 0; newI < i; newI++) {
                            newIndices[newI] = new int[groupIndices[newI].length];
                            System.arraycopy(groupIndices[newI], 0, newIndices[newI], 0, groupIndices[newI].length);
                        }
                        for (int newI = i + 1; newI < groupIndices.length; newI++) {
                            newIndices[newI - 1] = new int[groupIndices[newI].length];
                            System.arraycopy(groupIndices[newI], 0, newIndices[newI - 1], 0,
                                    groupIndices[newI].length);
                        }
                    } else {
                        // deleted cell is contained in a group with more than 2 cells => keep the group and delete only the item
                        newIndices = new int[groupIndices.length][];
                        for (int newI = 0; newI < groupIndices.length; newI++) {
                            if (newI == i) {
                                newIndices[newI] = new int[groupIndices[newI].length - 1];
                                System.arraycopy(groupIndices[newI], 0, newIndices[newI], 0, j);
                                System.arraycopy(groupIndices[newI], j + 1, newIndices[newI], j,
                                        groupIndices[i].length - j - 1);
                            } else {
                                newIndices[newI] = new int[groupIndices[newI].length];
                                System.arraycopy(groupIndices[newI], 0, newIndices[newI], 0,
                                        groupIndices[i].length);
                            }
                        }
                    }
                    return newIndices;
                }
            }
        }
        return groupIndices;
    }

    @Override
    @Nullable
    public String getCellResizeTooltip(RadContainer container, boolean isRow, int cell, int newSize) {
        final String size = getUpdatedSize(container, isRow, cell, newSize).toString();
        return isRow ? UIDesignerBundle.message("tooltip.resize.row", cell + getCellIndexBase(), size)
                : UIDesignerBundle.message("tooltip.resize.column", cell + getCellIndexBase(), size);
    }

    @Override
    public void processCellResized(RadContainer container, final boolean isRow, final int cell, final int newSize) {
        FormLayout formLayout = (FormLayout) container.getLayout();
        final ConstantSize updatedSize = getUpdatedSize(container, isRow, cell, newSize);
        FormSpec newSpec;
        if (isRow) {
            RowSpec rowSpec = formLayout.getRowSpec(cell + 1);
            newSpec = new RowSpec(rowSpec.getDefaultAlignment(), updatedSize, rowSpec.getResizeWeight());
        } else {
            ColumnSpec colSpec = formLayout.getColumnSpec(cell + 1);
            newSpec = new ColumnSpec(colSpec.getDefaultAlignment(), updatedSize, colSpec.getResizeWeight());
        }
        setSpec(formLayout, newSpec, cell + 1, isRow);
        resizeSameGroupCells(cell, formLayout, newSpec, isRow);
    }

    // Explicitly resize all cells in the group to desired size to make sure that the resize operation is effective (IDEADEV-10202) 
    private static void resizeSameGroupCells(final int cell, final FormLayout formLayout, final FormSpec newSpec,
            final boolean isRow) {
        int[][] groups = isRow ? formLayout.getRowGroups() : formLayout.getColumnGroups();
        for (int[] group : groups) {
            boolean foundGroup = false;
            for (int groupCell : group) {
                if (groupCell == cell + 1) {
                    foundGroup = true;
                    break;
                }
            }
            if (foundGroup) {
                for (int groupCell : group) {
                    setSpec(formLayout, newSpec, groupCell, isRow);
                }
                break;
            }
        }
    }

    private static void setSpec(final FormLayout formLayout, final FormSpec newSpec, final int cell,
            boolean isRow) {
        if (isRow) {
            formLayout.setRowSpec(cell, (RowSpec) newSpec);
        } else {
            formLayout.setColumnSpec(cell, (ColumnSpec) newSpec);
        }
    }

    private static ConstantSize getUpdatedSize(RadContainer container, boolean isRow, int cell, int newPx) {
        FormLayout formLayout = (FormLayout) container.getLayout();
        if (isRow) {
            return scaleSize(formLayout.getRowSpec(cell + 1), container, newPx);
        } else {
            return scaleSize(formLayout.getColumnSpec(cell + 1), container, newPx);
        }
    }

    private static ConstantSize scaleSize(final FormSpec rowSpec, final RadContainer container, final int newPx) {
        if (rowSpec.getSize() instanceof ConstantSize) {
            ConstantSize oldSize = (ConstantSize) rowSpec.getSize();
            int oldPx = oldSize.getPixelSize(container.getDelegee());
            double newValue = Math.round(oldSize.getValue() * newPx / oldPx * 10) / 10;
            return new ConstantSize(newValue, oldSize.getUnit());
        }
        return new ConstantSize(newPx, ConstantSize.PIXEL);
    }

    @Override
    public void processCellsMoved(final RadContainer container, final boolean isRow, final int[] cellsToMove,
            int targetCell) {
        for (int i = 0; i < cellsToMove.length; i++) {
            final int sourceCell = cellsToMove[i];
            moveCells(container, isRow, sourceCell, targetCell);
            if (sourceCell < targetCell) {
                for (int j = i + 1; j < cellsToMove.length; j++) {
                    cellsToMove[j] -= 2;
                }
            } else {
                targetCell += 2;
            }
        }
    }

    private void moveCells(final RadContainer container, final boolean isRow, final int cell, int targetCell) {
        if (targetCell >= cell && targetCell <= cell + 2) {
            return;
        }
        FormLayout layout = (FormLayout) container.getLayout();
        List<RadComponent> componentsToMove = new ArrayList<RadComponent>();
        FormSpec oldSpec = isRow ? layout.getRowSpec(cell + 1) : layout.getColumnSpec(cell + 1);
        for (RadComponent c : container.getComponents()) {
            if (c.getConstraints().getCell(isRow) == cell) {
                componentsToMove.add(c);
                container.removeComponent(c);
            }
        }
        int count = deleteGridCells(container, cell, isRow);
        int insertCell = (targetCell > cell) ? targetCell - count - 1 : targetCell;
        final boolean isBefore = targetCell < cell;
        int newIndex = insertGridCells(container, insertCell, isRow, isBefore, oldSpec);
        for (RadComponent c : componentsToMove) {
            c.getConstraints().setCell(isRow, newIndex);
            container.addComponent(c);
        }
    }

    private static void updateGridConstraintsFromCellConstraints(RadContainer grid) {
        FormLayout layout = (FormLayout) grid.getLayout();
        for (RadComponent c : grid.getComponents()) {
            CellConstraints cc = layout.getConstraints(c.getDelegee());
            GridConstraints gc = c.getConstraints();
            copyCellToGridConstraints(gc, cc);
        }
    }

    private static void copyCellToGridConstraints(final GridConstraints gc, final CellConstraints cc) {
        gc.setColumn(cc.gridX - 1);
        gc.setRow(cc.gridY - 1);
        gc.setColSpan(cc.gridWidth);
        gc.setRowSpan(cc.gridHeight);
    }

    public int getAlignment(RadComponent component, boolean horizontal) {
        CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
        CellConstraints.Alignment al = horizontal ? cc.hAlign : cc.vAlign;
        if (al == CellConstraints.DEFAULT) {
            FormLayout formLayout = (FormLayout) component.getParent().getLayout();
            FormSpec formSpec = horizontal ? formLayout.getColumnSpec(component.getConstraints().getColumn() + 1)
                    : formLayout.getRowSpec(component.getConstraints().getRow() + 1);
            final FormSpec.DefaultAlignment defaultAlignment = formSpec.getDefaultAlignment();
            if (defaultAlignment.equals(RowSpec.FILL)) {
                return GridConstraints.ALIGN_FILL;
            }
            if (defaultAlignment.equals(RowSpec.TOP) || defaultAlignment.equals(ColumnSpec.LEFT)) {
                return GridConstraints.ALIGN_LEFT;
            }
            if (defaultAlignment.equals(RowSpec.CENTER)) {
                return GridConstraints.ALIGN_CENTER;
            }
            return GridConstraints.ALIGN_RIGHT;
        }
        return Utils.alignFromConstraints(component.getConstraints(), horizontal);
    }

    public void setAlignment(RadComponent component, boolean horizontal, int alignment) {
        CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
        if (horizontal) {
            cc.hAlign = FormLayoutSerializer.ourHorizontalAlignments[alignment];
        } else {
            cc.vAlign = FormLayoutSerializer.ourVerticalAlignments[alignment];
        }
        updateConstraints(component);
    }

    public void resetAlignment(RadComponent component, boolean horizontal) {
        CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
        if (horizontal) {
            cc.hAlign = CellConstraints.DEFAULT;
        } else {
            cc.vAlign = CellConstraints.DEFAULT;
        }
        updateConstraints(component);
    }

    public boolean isAlignmentModified(RadComponent component, boolean horizontal) {
        CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
        CellConstraints.Alignment al = horizontal ? cc.hAlign : cc.vAlign;
        return al != CellConstraints.DEFAULT;
    }

    @Override
    protected void updateConstraints(final RadComponent component) {
        FormLayout layout = (FormLayout) component.getParent().getLayout();
        layout.setConstraints(component.getDelegee(), gridToCellConstraints(component));
        super.updateConstraints(component);
    }

    public int getMinCellCount() {
        return 0;
    }

    @Override
    public void createSnapshotLayout(final SnapshotContext context, final JComponent parent,
            final RadContainer container, final LayoutManager layout) {
        ColumnSpec[] colSpecs;
        RowSpec[] rowSpecs;
        int[][] rowGroups;
        int[][] columnGroups;
        try {
            Method method = layout.getClass().getMethod("getRowCount", ArrayUtil.EMPTY_CLASS_ARRAY);
            int rowCount = ((Integer) method.invoke(layout, ArrayUtil.EMPTY_OBJECT_ARRAY)).intValue();
            method = layout.getClass().getMethod("getColumnCount", ArrayUtil.EMPTY_CLASS_ARRAY);
            int columnCount = ((Integer) method.invoke(layout, ArrayUtil.EMPTY_OBJECT_ARRAY)).intValue();

            rowSpecs = new RowSpec[rowCount];
            colSpecs = new ColumnSpec[columnCount];

            method = layout.getClass().getMethod("getRowSpec", int.class);
            for (int i = 0; i < rowCount; i++) {
                rowSpecs[i] = (RowSpec) createSerializedCopy(method.invoke(layout, i + 1));
            }
            method = layout.getClass().getMethod("getColumnSpec", int.class);
            for (int i = 0; i < columnCount; i++) {
                colSpecs[i] = (ColumnSpec) createSerializedCopy(method.invoke(layout, i + 1));
            }

            method = layout.getClass().getMethod("getRowGroups", ArrayUtil.EMPTY_CLASS_ARRAY);
            rowGroups = (int[][]) method.invoke(layout);

            method = layout.getClass().getMethod("getColumnGroups", ArrayUtil.EMPTY_CLASS_ARRAY);
            columnGroups = (int[][]) method.invoke(layout);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        final FormLayout formLayout = new FormLayout(colSpecs, rowSpecs);
        formLayout.setRowGroups(rowGroups);
        formLayout.setColumnGroups(columnGroups);
        container.setLayout(formLayout);
    }

    private static Object createSerializedCopy(final Object original) {
        // FormLayout may have been loaded with a different classloader, so we need to create a copy through serialization
        Object copy;
        try {
            BufferExposingByteArrayOutputStream baos = new BufferExposingByteArrayOutputStream();
            ObjectOutputStream os = new ObjectOutputStream(baos);
            try {
                os.writeObject(original);
            } finally {
                os.close();
            }

            InputStream bais = new ByteArrayInputStream(baos.getInternalBuffer(), 0, baos.size());
            ObjectInputStream is = new ObjectInputStream(bais);
            try {
                copy = is.readObject();
            } finally {
                is.close();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return copy;
    }

    @Override
    public void addSnapshotComponent(final JComponent parent, final JComponent child, final RadContainer container,
            final RadComponent component) {
        CellConstraints cc;
        try {
            LayoutManager layout = parent.getLayout();
            //noinspection HardCodedStringLiteral
            Method method = layout.getClass().getMethod("getConstraints", Component.class);
            cc = (CellConstraints) createSerializedCopy(method.invoke(layout, child));
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        copyCellToGridConstraints(component.getConstraints(), cc);
        component.setCustomLayoutConstraints(cc);
        container.addComponent(component);
    }

    private static class ComponentInsetsProperty extends AbstractInsetsProperty<RadComponent> {
        public ComponentInsetsProperty() {
            super(null, "Insets");
        }

        public Insets getValue(final RadComponent component) {
            if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
                final CellConstraints cellConstraints = (CellConstraints) component.getCustomLayoutConstraints();
                return cellConstraints.insets;
            }
            return new Insets(0, 0, 0, 0);
        }

        protected void setValueImpl(final RadComponent component, final Insets value) throws Exception {
            if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
                final CellConstraints cellConstraints = (CellConstraints) component.getCustomLayoutConstraints();
                cellConstraints.insets = value;

                FormLayout layout = (FormLayout) component.getParent().getLayout();
                CellConstraints cc = (CellConstraints) layout.getConstraints(component.getDelegee()).clone();
                cc.insets = value;
                layout.setConstraints(component.getDelegee(), cc);
            }
        }
    }
}