update copyright
[fedora-idea.git] / plugins / ui-designer / src / com / intellij / uiDesigner / radComponents / RadFormLayoutManager.java
blob0b43cbafbc21fc552e8f43f175714cddaaf2b571
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.uiDesigner.radComponents;
19 import com.intellij.openapi.actionSystem.ActionGroup;
20 import com.intellij.openapi.actionSystem.DefaultActionGroup;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.uiDesigner.GridChangeUtil;
23 import com.intellij.uiDesigner.UIDesignerBundle;
24 import com.intellij.uiDesigner.UIFormXmlConstants;
25 import com.intellij.uiDesigner.XmlWriter;
26 import com.intellij.uiDesigner.actions.*;
27 import com.intellij.uiDesigner.compiler.FormLayoutUtils;
28 import com.intellij.uiDesigner.compiler.Utils;
29 import com.intellij.uiDesigner.core.GridConstraints;
30 import com.intellij.uiDesigner.designSurface.*;
31 import com.intellij.uiDesigner.lw.FormLayoutSerializer;
32 import com.intellij.uiDesigner.propertyInspector.Property;
33 import com.intellij.uiDesigner.propertyInspector.properties.AbstractInsetsProperty;
34 import com.intellij.uiDesigner.propertyInspector.properties.AlignPropertyProvider;
35 import com.intellij.uiDesigner.propertyInspector.properties.HorzAlignProperty;
36 import com.intellij.uiDesigner.propertyInspector.properties.VertAlignProperty;
37 import com.intellij.uiDesigner.snapShooter.SnapshotContext;
38 import com.intellij.util.ArrayUtil;
39 import com.jgoodies.forms.factories.FormFactory;
40 import com.jgoodies.forms.layout.*;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
45 import javax.swing.*;
46 import java.awt.*;
47 import java.beans.PropertyChangeEvent;
48 import java.beans.PropertyChangeListener;
49 import java.io.*;
50 import java.lang.reflect.Method;
51 import java.util.ArrayList;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Map;
56 /**
57 * @author yole
59 public class RadFormLayoutManager extends RadAbstractGridLayoutManager implements AlignPropertyProvider {
60 private FormLayoutColumnProperties myPropertiesPanel;
61 private final Map<RadComponent, MyPropertyChangeListener> myListenerMap = new HashMap<RadComponent, MyPropertyChangeListener>();
63 @NonNls private static final String ENCODED_FORMSPEC_GROW = "d:grow";
64 private static final Size DEFAULT_NOGROW_SIZE = new BoundedSize(Sizes.DEFAULT, new ConstantSize(4, ConstantSize.PIXEL), null);
66 @Nullable public String getName() {
67 return UIFormXmlConstants.LAYOUT_FORM;
70 @Override @Nullable
71 public LayoutManager createLayout() {
72 return new FormLayout(ENCODED_FORMSPEC_GROW, ENCODED_FORMSPEC_GROW);
75 @Override
76 protected void changeLayoutFromGrid(final RadContainer container, final List<RadComponent> contents, final List<Boolean> canRowsGrow,
77 final List<Boolean> canColumnsGrow) {
78 int rowCount = canRowsGrow.size();
79 int columnCount = canColumnsGrow.size();
80 int rowCountWithGaps = (rowCount == 0) ? 0 : rowCount * 2 - 1;
81 int columnCountWithGaps = (columnCount == 0) ? 0 : columnCount * 2 - 1;
82 RowSpec[] rowSpecs = new RowSpec [rowCountWithGaps];
83 ColumnSpec[] colSpecs = new ColumnSpec [columnCountWithGaps];
85 for(int i=0; i<rowCount; i++) {
86 rowSpecs [i*2] = canRowsGrow.get(i).booleanValue() ? new RowSpec(ENCODED_FORMSPEC_GROW) : new RowSpec(DEFAULT_NOGROW_SIZE);
87 if (i*2+1 < rowSpecs.length) {
88 rowSpecs [i*2+1] = FormFactory.RELATED_GAP_ROWSPEC;
91 for(int i=0; i<columnCount; i++) {
92 colSpecs [i*2] = canColumnsGrow.get(i).booleanValue() ? new ColumnSpec(ENCODED_FORMSPEC_GROW) : new ColumnSpec(DEFAULT_NOGROW_SIZE);
93 if (i*2+1 < colSpecs.length) {
94 colSpecs [i*2+1] = FormFactory.RELATED_GAP_COLSPEC;
98 container.setLayoutManager(this, new FormLayout(colSpecs, rowSpecs));
101 @Override
102 protected void changeLayoutFromIndexed(final RadContainer container, final List<RadComponent> components) {
103 int maxSizePolicy = 0;
104 for(RadComponent c: components) {
105 maxSizePolicy = Math.max(maxSizePolicy, c.getConstraints().getHSizePolicy());
107 ColumnSpec[] colSpecs = new ColumnSpec [components.size() * 2 - 1];
108 for(int i=0; i<components.size(); i++) {
109 colSpecs [i*2] = components.get(i).getConstraints().getHSizePolicy() == maxSizePolicy
110 ? new ColumnSpec(ENCODED_FORMSPEC_GROW)
111 : FormFactory.DEFAULT_COLSPEC;
112 if (i*2+1 < colSpecs.length) {
113 colSpecs [i*2+1] = FormFactory.RELATED_GAP_COLSPEC;
116 container.setLayoutManager(this, new FormLayout(colSpecs, new RowSpec[] { FormFactory.DEFAULT_ROWSPEC } ));
119 @Override
120 public void writeLayout(final XmlWriter writer, final RadContainer radContainer) {
121 FormLayout layout = (FormLayout) radContainer.getLayout();
122 for(int i=1; i<=layout.getRowCount(); i++) {
123 RowSpec rowSpec = layout.getRowSpec(i);
124 writer.startElement(UIFormXmlConstants.ELEMENT_ROWSPEC);
125 try {
126 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_VALUE, FormLayoutUtils.getEncodedSpec(rowSpec));
128 finally {
129 writer.endElement();
132 for(int i=1; i<=layout.getColumnCount(); i++) {
133 ColumnSpec columnSpec = layout.getColumnSpec(i);
134 writer.startElement(UIFormXmlConstants.ELEMENT_COLSPEC);
135 try {
136 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_VALUE, FormLayoutUtils.getEncodedSpec(columnSpec));
138 finally {
139 writer.endElement();
142 writeGroups(writer, UIFormXmlConstants.ELEMENT_ROWGROUP, layout.getRowGroups());
143 writeGroups(writer, UIFormXmlConstants.ELEMENT_COLGROUP, layout.getColumnGroups());
146 private static void writeGroups(final XmlWriter writer, final String elementName, final int[][] groups) {
147 for(int[] group: groups) {
148 writer.startElement(elementName);
149 try {
150 for(int member: group) {
151 writer.startElement(UIFormXmlConstants.ELEMENT_MEMBER);
152 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_INDEX, member);
153 writer.endElement();
156 finally {
157 writer.endElement();
162 @Override
163 public void addComponentToContainer(final RadContainer container, final RadComponent component, final int index) {
164 MyPropertyChangeListener listener = new MyPropertyChangeListener(component);
165 myListenerMap.put(component, listener);
166 component.addPropertyChangeListener(listener);
167 final CellConstraints cc = gridToCellConstraints(component);
168 if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
169 CellConstraints customCellConstraints = (CellConstraints) component.getCustomLayoutConstraints();
170 cc.insets = customCellConstraints.insets;
172 component.setCustomLayoutConstraints(cc);
173 container.getDelegee().add(component.getDelegee(), cc, index);
176 @Override
177 public void removeComponentFromContainer(final RadContainer container, final RadComponent component) {
178 final MyPropertyChangeListener listener = myListenerMap.get(component);
179 if (listener != null) {
180 component.removePropertyChangeListener(listener);
181 myListenerMap.remove(component);
183 super.removeComponentFromContainer(container, component);
186 private static CellConstraints gridToCellConstraints(final RadComponent component) {
187 GridConstraints gc = component.getConstraints();
188 CellConstraints.Alignment hAlign = ((gc.getHSizePolicy() & GridConstraints.SIZEPOLICY_WANT_GROW) != 0)
189 ? CellConstraints.FILL
190 : CellConstraints.DEFAULT;
191 CellConstraints.Alignment vAlign = ((gc.getVSizePolicy() & GridConstraints.SIZEPOLICY_WANT_GROW) != 0)
192 ? CellConstraints.FILL
193 : CellConstraints.DEFAULT;
194 if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
195 CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
196 hAlign = cc.hAlign;
197 vAlign = cc.vAlign;
199 return new CellConstraints(gc.getColumn()+1, gc.getRow()+1, gc.getColSpan(), gc.getRowSpan(), hAlign, vAlign);
202 @Override
203 public void writeChildConstraints(final XmlWriter writer, final RadComponent child) {
204 writeGridConstraints(writer, child);
205 if (child.getCustomLayoutConstraints() instanceof CellConstraints) {
206 CellConstraints cc = (CellConstraints) child.getCustomLayoutConstraints();
207 writer.startElement(UIFormXmlConstants.ELEMENT_FORMS);
208 try {
209 if (!cc.insets.equals(new Insets(0, 0, 0, 0))) {
210 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TOP, cc.insets.top);
211 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_LEFT, cc.insets.left);
212 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_BOTTOM, cc.insets.bottom);
213 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_RIGHT, cc.insets.right);
215 if (cc.hAlign != CellConstraints.DEFAULT) {
216 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_DEFAULTALIGN_HORZ, false);
218 if (cc.vAlign != CellConstraints.DEFAULT) {
219 writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_DEFAULTALIGN_VERT, false);
222 finally {
223 writer.endElement();
228 private static FormLayout getFormLayout(final RadContainer container) {
229 return (FormLayout) container.getLayout();
232 @Override public int getGridRowCount(RadContainer container) {
233 return getFormLayout(container).getRowCount();
236 @Override public int getGridColumnCount(RadContainer container) {
237 return getFormLayout(container).getColumnCount();
240 @Override public int[] getGridCellCoords(RadContainer container, boolean isRow) {
241 final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
242 int[] origins = isRow ? layoutInfo.rowOrigins : layoutInfo.columnOrigins;
243 int[] result = new int [origins.length-1];
244 System.arraycopy(origins, 0, result, 0, result.length);
245 return result;
248 @Override public int[] getGridCellSizes(RadContainer container, boolean isRow) {
249 final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
250 int[] origins = isRow ? layoutInfo.rowOrigins : layoutInfo.columnOrigins;
251 int[] result = new int [origins.length-1];
252 for(int i=0; i<result.length; i++) {
253 result [i] = origins [i+1] - origins [i];
255 return result;
258 @Override public int[] getHorizontalGridLines(RadContainer container) {
259 final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
260 return layoutInfo.rowOrigins;
263 @Override public int[] getVerticalGridLines(RadContainer container) {
264 final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
265 return layoutInfo.columnOrigins;
268 @Override public int getGridRowAt(RadContainer container, int y) {
269 final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
270 return findCell(layoutInfo.rowOrigins, y);
273 @Override public int getGridColumnAt(RadContainer container, int x) {
274 final FormLayout.LayoutInfo layoutInfo = getFormLayout(container).getLayoutInfo(container.getDelegee());
275 return findCell(layoutInfo.columnOrigins, x);
278 private static int findCell(final int[] origins, final int coord) {
279 for(int i=0; i<origins.length-1; i++) {
280 if (coord >= origins [i] && coord < origins [i+1]) return i;
282 return -1;
285 @NotNull @Override
286 public ComponentDropLocation getDropLocation(@NotNull RadContainer container, @Nullable final Point location) {
287 FormLayout formLayout = getFormLayout(container);
288 if (formLayout.getRowCount() == 0 || formLayout.getColumnCount() == 0) {
289 if (location != null) {
290 Rectangle rc = new Rectangle(new Point(), container.getDelegee().getSize());
291 return new FormFirstComponentInsertLocation(container, location, rc);
294 final FormLayout.LayoutInfo layoutInfo = formLayout.getLayoutInfo(container.getDelegee());
295 if (location != null && location.x > layoutInfo.getWidth()) {
296 int row = findCell(layoutInfo.rowOrigins, location.y);
297 if (row == -1) {
298 return NoDropLocation.INSTANCE;
300 return new GridInsertLocation(container, row, getGridColumnCount(container)-1, GridInsertMode.ColumnAfter);
302 if (location != null && location.y > layoutInfo.getHeight()) {
303 int column = findCell(layoutInfo.columnOrigins, location.x);
304 if (column == -1) {
305 return NoDropLocation.INSTANCE;
307 return new GridInsertLocation(container, getGridRowCount(container)-1, column, GridInsertMode.RowAfter);
310 if (container.getGridRowCount() == 1 && container.getGridColumnCount() == 1 &&
311 getComponentAtGrid(container, 0, 0) == null) {
312 final Rectangle rc = getGridCellRangeRect(container, 0, 0, 0, 0);
313 if (location == null) {
314 return new FormFirstComponentInsertLocation(container, rc, 0, 0);
316 return new FormFirstComponentInsertLocation(container, location, rc);
319 return super.getDropLocation(container, location);
322 @Override
323 public CustomPropertiesPanel getRowColumnPropertiesPanel(RadContainer container, boolean isRow, int[] selectedIndices) {
324 if (myPropertiesPanel == null) {
325 myPropertiesPanel = new FormLayoutColumnProperties();
327 myPropertiesPanel.showProperties(container, isRow, selectedIndices);
328 return myPropertiesPanel;
331 @Override
332 public ActionGroup getCaptionActions() {
333 DefaultActionGroup group = new DefaultActionGroup();
334 group.add(new InsertBeforeAction());
335 group.add(new InsertAfterAction());
336 group.add(new SplitAction());
337 group.add(new DeleteAction());
338 group.add(new GroupRowsColumnsAction());
339 group.add(new UngroupRowsColumnsAction());
340 return group;
343 @Override
344 public boolean canCellGrow(RadContainer container, boolean isRow, int index) {
345 FormLayout layout = (FormLayout) container.getLayout();
346 FormSpec spec = isRow ? layout.getRowSpec(index+1) : layout.getColumnSpec(index+1);
347 return spec.getResizeWeight() > 0.01d;
350 @Override
351 public void setChildDragging(RadComponent child, boolean dragging) {
352 // do nothing here - otherwise the layout will jump around
355 @Override
356 public void paintCaptionDecoration(final RadContainer container, final boolean isRow, final int index, final Graphics2D g2d,
357 final Rectangle rc) {
358 // don't paint gap rows/columns with red background
359 if (isGapCell(container, isRow, index)) {
360 g2d.setColor(Color.LIGHT_GRAY);
361 g2d.fillRect(rc.x, rc.y, rc.width, rc.height);
364 if (canCellGrow(container, isRow, index)) {
365 drawGrowMarker(isRow, g2d, rc);
368 FormLayout layout = (FormLayout) container.getLayout();
369 int[][] groups = isRow ? layout.getRowGroups() : layout.getColumnGroups();
370 //noinspection MultipleVariablesInDeclaration
371 boolean haveTopLeft = false, haveTopRight = false, haveTopLine = false;
372 //noinspection MultipleVariablesInDeclaration
373 boolean haveBottomLeft = false, haveBottomRight = false, haveBottomLine = false;
374 boolean inGroup = false;
375 for(int i=0; i<groups.length; i++) {
376 int minMember = Integer.MAX_VALUE;
377 int maxMember = -1;
378 for(int member: groups [i]) {
379 minMember = Math.min(member-1, minMember);
380 maxMember = Math.max(member-1, maxMember);
381 inGroup = inGroup || (member-1 == index);
383 if (minMember <= index && index <= maxMember) {
384 if (i % 2 == 0) {
385 haveTopLeft = haveTopLeft || index > minMember;
386 haveTopRight = haveTopRight || index < maxMember;
387 haveTopLine = haveTopLine || inGroup;
389 else {
390 haveBottomLeft = haveBottomLeft || index > minMember;
391 haveBottomRight = haveBottomRight || index < maxMember;
392 haveBottomLine = haveBottomLine || inGroup;
397 g2d.setColor(Color.BLUE);
398 drawGroupLine(rc, isRow, g2d, true, haveTopLeft, haveTopRight, haveTopLine);
399 drawGroupLine(rc, isRow, g2d, false, haveBottomLeft, haveBottomRight, haveBottomLine);
402 private static void drawGroupLine(final Rectangle rc, final boolean isRow, final Graphics2D g2d, boolean isTop,
403 final boolean haveLeft, final boolean haveRight, final boolean haveLine) {
405 int maxX = (int) rc.getMaxX();
406 int maxY = (int) rc.getMaxY();
407 Point linePos = isTop ? new Point(rc.x+1, rc.y+1) : new Point(rc.x+3, rc.y+3);
408 Point markerPos = new Point(rc.x+6, rc.y+6);
410 int midX = (int) rc.getCenterX();
411 int midY = (int) rc.getCenterY();
412 if (haveLine) {
413 if (isRow) {
414 g2d.drawLine(linePos.x, midY, markerPos.x, midY);
416 else {
417 g2d.drawLine(midX, linePos.y, midX, markerPos.y);
420 if (haveLeft) {
421 if (isRow) {
422 g2d.drawLine(linePos.x, rc.y, linePos.x, midY);
424 else {
425 g2d.drawLine(rc.x, linePos.y, midX, linePos.y);
428 if (haveRight) {
429 if (isRow) {
430 g2d.drawLine(linePos.x, midY, linePos.x, maxY);
432 else {
433 g2d.drawLine(midX, linePos.y, maxX, linePos.y);
438 @Override
439 public Property[] getContainerProperties(final Project project) {
440 return Property.EMPTY_ARRAY;
443 @Override
444 public Property[] getComponentProperties(final Project project, final RadComponent component) {
445 return new Property[] {
446 HorzAlignProperty.getInstance(project),
447 VertAlignProperty.getInstance(project),
448 new ComponentInsetsProperty()
452 @Override
453 public int insertGridCells(final RadContainer grid, final int cellIndex, final boolean isRow, final boolean isBefore, final boolean grow) {
454 FormSpec formSpec;
455 if (isRow) {
456 formSpec = grow ? new RowSpec(ENCODED_FORMSPEC_GROW) : new RowSpec(DEFAULT_NOGROW_SIZE);
458 else {
459 formSpec = grow ? new ColumnSpec(ENCODED_FORMSPEC_GROW) : new ColumnSpec(DEFAULT_NOGROW_SIZE);
461 insertGridCells(grid, cellIndex, isRow, isBefore, formSpec);
462 return getGridCellCount(grid, isRow) == 1 ? 1 : 2;
465 @Override
466 public void copyGridCells(RadContainer source, final RadContainer destination, final boolean isRow, int cellIndex, int cellCount, int targetIndex) {
467 FormLayout sourceLayout = getFormLayout(source);
468 FormLayout destinationLayout = getFormLayout(destination);
469 if (isRow) {
470 insertOrAppendRow(destinationLayout, targetIndex+1, FormFactory.RELATED_GAP_ROWSPEC);
472 else {
473 insertOrAppendColumn(destinationLayout, targetIndex+1, FormFactory.RELATED_GAP_COLSPEC);
475 targetIndex++;
476 if (targetIndex < cellIndex) cellIndex++;
477 copyFormSpecs(sourceLayout, destinationLayout, isRow, cellIndex, cellCount, targetIndex);
478 updateGridConstraintsFromCellConstraints(destination);
481 private static void copyFormSpecs(final FormLayout sourceLayout,
482 final FormLayout destinationLayout,
483 final boolean isRow,
484 int cellIndex,
485 int cellCount,
486 int targetIndex) {
487 for(int i=0; i < cellCount; i++) {
488 if (isRow) {
489 RowSpec rowSpec = sourceLayout.getRowSpec(cellIndex + 1);
490 insertOrAppendRow(destinationLayout, targetIndex+1, rowSpec);
492 else {
493 ColumnSpec colSpec = sourceLayout.getColumnSpec(cellIndex + 1);
494 insertOrAppendColumn(destinationLayout, targetIndex+1, colSpec);
496 cellIndex += (targetIndex < cellIndex && sourceLayout == destinationLayout) ? 2 : 1;
497 targetIndex++;
501 @Override
502 public void copyGridSection(final RadContainer source, final RadContainer destination, final Rectangle rc) {
503 final FormLayout destinationLayout = new FormLayout();
504 destination.setLayout(destinationLayout);
505 copyFormSpecs(getFormLayout(source), destinationLayout, true, rc.y, rc.height, 0);
506 copyFormSpecs(getFormLayout(source), destinationLayout, false, rc.x, rc.width, 0);
509 @Override
510 public int getGapCellCount() {
511 return 1;
514 @Override
515 public int getGapCellSize(final RadContainer container, boolean isRow) {
516 Size size = isRow ? FormFactory.RELATED_GAP_ROWSPEC.getSize() : FormFactory.RELATED_GAP_COLSPEC.getSize();
517 if (size instanceof ConstantSize) {
518 return ((ConstantSize) size).getPixelSize(container.getDelegee());
520 return 0;
523 @Override
524 public boolean isGapCell(RadContainer grid, boolean isRow, int cellIndex) {
525 if (cellIndex < 0 || cellIndex >= getGridCellCount(grid, isRow)) {
526 return false;
528 if (cellIndex % 2 == 1) {
529 final GridChangeUtil.CellStatus status = GridChangeUtil.canDeleteCell(grid, cellIndex, isRow);
530 if (status == GridChangeUtil.CellStatus.Empty || status == GridChangeUtil.CellStatus.Redundant) {
531 return true;
534 return false;
537 @Override
538 public int getCellIndexBase() {
539 return 1;
543 * @return index where new column or row was actually inserted (0-based)
545 private int insertGridCells(RadContainer grid, int cellIndex, boolean isRow, boolean isBefore, FormSpec formSpec) {
546 FormLayout formLayout = (FormLayout) grid.getLayout();
547 int index = isBefore ? cellIndex+1 : cellIndex+2;
548 if (isRow) {
549 if (getGridCellCount(grid, true) > 0) {
550 insertOrAppendRow(formLayout, index, FormFactory.RELATED_GAP_ROWSPEC);
551 if (!isBefore) index++;
553 insertOrAppendRow(formLayout, index, (RowSpec) formSpec);
555 else {
556 if (getGridCellCount(grid, false) > 0) {
557 insertOrAppendColumn(formLayout, index, FormFactory.RELATED_GAP_COLSPEC);
558 if (!isBefore) index++;
560 insertOrAppendColumn(formLayout, index, (ColumnSpec)formSpec);
562 updateGridConstraintsFromCellConstraints(grid);
563 return index-1;
566 private static void insertOrAppendRow(final FormLayout formLayout, final int index, final RowSpec rowSpec) {
567 if (index == formLayout.getRowCount()+1) {
568 formLayout.appendRow(rowSpec);
570 else {
571 formLayout.insertRow(index, rowSpec);
575 private static void insertOrAppendColumn(final FormLayout formLayout, final int index, final ColumnSpec columnSpec) {
576 if (index == formLayout.getColumnCount()+1) {
577 formLayout.appendColumn(columnSpec);
579 else {
580 formLayout.insertColumn(index, columnSpec);
584 @Override
585 public int deleteGridCells(final RadContainer grid, final int cellIndex, final boolean isRow) {
586 int result = 1;
587 FormLayout formLayout = (FormLayout) grid.getLayout();
588 adjustDeletedCellOrigins(grid, cellIndex, isRow);
589 if (isRow) {
590 int[][] groupIndices = formLayout.getRowGroups();
591 groupIndices = removeDeletedCell(groupIndices, cellIndex+1);
592 formLayout.setRowGroups(groupIndices);
593 formLayout.removeRow(cellIndex+1);
594 updateGridConstraintsFromCellConstraints(grid);
595 if (formLayout.getRowCount() > 0 && formLayout.getRowCount() % 2 == 0) {
596 int gapRowIndex = (cellIndex >= grid.getGridRowCount()) ? cellIndex-1 : cellIndex;
597 if (GridChangeUtil.isRowEmpty(grid, gapRowIndex)) {
598 formLayout.removeRow(gapRowIndex+1);
599 updateGridConstraintsFromCellConstraints(grid);
600 result++;
604 else {
605 int[][] groupIndices = formLayout.getColumnGroups();
606 groupIndices = removeDeletedCell(groupIndices, cellIndex+1);
607 formLayout.setColumnGroups(groupIndices);
608 formLayout.removeColumn(cellIndex+1);
609 updateGridConstraintsFromCellConstraints(grid);
610 if (formLayout.getColumnCount() > 0 && formLayout.getColumnCount() % 2 == 0) {
611 int gapColumnIndex = (cellIndex >= grid.getGridColumnCount()) ? cellIndex-1 : cellIndex;
612 if (GridChangeUtil.isColumnEmpty(grid, gapColumnIndex)) {
613 formLayout.removeColumn(gapColumnIndex+1);
614 updateGridConstraintsFromCellConstraints(grid);
615 result++;
619 return result;
622 private void adjustDeletedCellOrigins(final RadContainer grid, final int cellIndex, final boolean isRow) {
623 int gapCellDelta = isGapCell(grid, isRow, cellIndex+1) ? 2 : 1;
624 for(RadComponent component: grid.getComponents()) {
625 // ensure that we don't have component origins in the deleted cells
626 final GridConstraints gc = component.getConstraints();
627 if (gc.getCell(isRow) == cellIndex) {
628 final int span = gc.getSpan(isRow);
629 if (span > gapCellDelta) {
630 gc.setCell(isRow, cellIndex+gapCellDelta);
631 gc.setSpan(isRow, span -gapCellDelta);
632 updateConstraints(component);
634 else {
635 throw new IllegalArgumentException("Attempt to delete grid row/column which contains origins of 1-span components");
641 private static int[][] removeDeletedCell(final int[][] groupIndices, final int deletedIndex) {
642 for(int i=0; i<groupIndices.length; i++) {
643 for(int j=0; j<groupIndices [i].length; j++) {
644 if (groupIndices [i][j] == deletedIndex) {
645 int[][] newIndices;
646 if (groupIndices [i].length <= 2) {
647 // deleted cell is contained in a group with 1 or 2 cells => delete entire group
648 newIndices = new int[groupIndices.length-1][];
649 for (int newI = 0; newI < i; newI++) {
650 newIndices [newI] = new int[groupIndices [newI].length];
651 System.arraycopy(groupIndices [newI], 0, newIndices [newI], 0, groupIndices [newI].length);
653 for(int newI=i+1; newI<groupIndices.length; newI++) {
654 newIndices [newI-1] = new int[groupIndices [newI].length];
655 System.arraycopy(groupIndices [newI], 0, newIndices [newI-1], 0, groupIndices [newI].length);
658 else {
659 // deleted cell is contained in a group with more than 2 cells => keep the group and delete only the item
660 newIndices = new int[groupIndices.length][];
661 for(int newI=0; newI<groupIndices.length; newI++) {
662 if (newI == i) {
663 newIndices [newI] = new int[groupIndices [newI].length-1];
664 System.arraycopy(groupIndices [newI], 0, newIndices [newI], 0, j);
665 System.arraycopy(groupIndices [newI], j+1, newIndices [newI], j, groupIndices [i].length-j-1);
667 else {
668 newIndices [newI] = new int[groupIndices [newI].length];
669 System.arraycopy(groupIndices [newI], 0, newIndices [newI], 0, groupIndices [i].length);
673 return newIndices;
677 return groupIndices;
680 @Override @Nullable
681 public String getCellResizeTooltip(RadContainer container, boolean isRow, int cell, int newSize) {
682 final String size = getUpdatedSize(container, isRow, cell, newSize).toString();
683 return isRow
684 ? UIDesignerBundle.message("tooltip.resize.row", cell+getCellIndexBase(), size)
685 : UIDesignerBundle.message("tooltip.resize.column", cell+getCellIndexBase(), size);
688 @Override
689 public void processCellResized(RadContainer container, final boolean isRow, final int cell, final int newSize) {
690 FormLayout formLayout = (FormLayout) container.getLayout();
691 final ConstantSize updatedSize = getUpdatedSize(container, isRow, cell, newSize);
692 FormSpec newSpec;
693 if (isRow) {
694 RowSpec rowSpec = formLayout.getRowSpec(cell+1);
695 newSpec = new RowSpec(rowSpec.getDefaultAlignment(), updatedSize, rowSpec.getResizeWeight());
697 else {
698 ColumnSpec colSpec = formLayout.getColumnSpec(cell+1);
699 newSpec = new ColumnSpec(colSpec.getDefaultAlignment(), updatedSize, colSpec.getResizeWeight());
701 setSpec(formLayout, newSpec, cell+1, isRow);
702 resizeSameGroupCells(cell, formLayout, newSpec, isRow);
705 // Explicitly resize all cells in the group to desired size to make sure that the resize operation is effective (IDEADEV-10202)
706 private static void resizeSameGroupCells(final int cell, final FormLayout formLayout, final FormSpec newSpec, final boolean isRow) {
707 int[][] groups = isRow ? formLayout.getRowGroups() : formLayout.getColumnGroups();
708 for(int[] group: groups) {
709 boolean foundGroup = false;
710 for(int groupCell: group) {
711 if (groupCell == cell+1) {
712 foundGroup = true;
713 break;
716 if (foundGroup) {
717 for(int groupCell: group) {
718 setSpec(formLayout, newSpec, groupCell, isRow);
720 break;
725 private static void setSpec(final FormLayout formLayout, final FormSpec newSpec, final int cell, boolean isRow) {
726 if (isRow) {
727 formLayout.setRowSpec(cell, (RowSpec) newSpec);
729 else {
730 formLayout.setColumnSpec(cell, (ColumnSpec) newSpec);
734 private static ConstantSize getUpdatedSize(RadContainer container, boolean isRow, int cell, int newPx) {
735 FormLayout formLayout = (FormLayout) container.getLayout();
736 if (isRow) {
737 return scaleSize(formLayout.getRowSpec(cell+1), container, newPx);
739 else {
740 return scaleSize(formLayout.getColumnSpec(cell+1), container, newPx);
744 private static ConstantSize scaleSize(final FormSpec rowSpec, final RadContainer container, final int newPx) {
745 if (rowSpec.getSize() instanceof ConstantSize) {
746 ConstantSize oldSize = (ConstantSize) rowSpec.getSize();
747 int oldPx = oldSize.getPixelSize(container.getDelegee());
748 double newValue = Math.round(oldSize.getValue() * newPx / oldPx * 10) / 10;
749 return new ConstantSize(newValue, oldSize.getUnit());
751 return new ConstantSize(newPx, ConstantSize.PIXEL);
754 @Override
755 public void processCellsMoved(final RadContainer container, final boolean isRow, final int[] cellsToMove, int targetCell) {
756 for(int i=0; i<cellsToMove.length; i++) {
757 final int sourceCell = cellsToMove[i];
758 moveCells(container, isRow, sourceCell, targetCell);
759 if (sourceCell < targetCell) {
760 for(int j=i+1; j<cellsToMove.length; j++) {
761 cellsToMove [j] -= 2;
764 else {
765 targetCell += 2;
770 private void moveCells(final RadContainer container, final boolean isRow, final int cell, int targetCell) {
771 if (targetCell >= cell && targetCell <= cell+2) {
772 return;
774 FormLayout layout = (FormLayout) container.getLayout();
775 List<RadComponent> componentsToMove = new ArrayList<RadComponent>();
776 FormSpec oldSpec = isRow ? layout.getRowSpec(cell+1) : layout.getColumnSpec(cell+1);
777 for(RadComponent c: container.getComponents()) {
778 if (c.getConstraints().getCell(isRow) == cell) {
779 componentsToMove.add(c);
780 container.removeComponent(c);
783 int count = deleteGridCells(container, cell, isRow);
784 int insertCell = (targetCell > cell) ? targetCell - count - 1 : targetCell;
785 final boolean isBefore = targetCell < cell;
786 int newIndex = insertGridCells(container, insertCell, isRow, isBefore, oldSpec);
787 for(RadComponent c: componentsToMove) {
788 c.getConstraints().setCell(isRow, newIndex);
789 container.addComponent(c);
793 private static void updateGridConstraintsFromCellConstraints(RadContainer grid) {
794 FormLayout layout = (FormLayout) grid.getLayout();
795 for(RadComponent c: grid.getComponents()) {
796 CellConstraints cc = layout.getConstraints(c.getDelegee());
797 GridConstraints gc = c.getConstraints();
798 copyCellToGridConstraints(gc, cc);
802 private static void copyCellToGridConstraints(final GridConstraints gc, final CellConstraints cc) {
803 gc.setColumn(cc.gridX-1);
804 gc.setRow(cc.gridY-1);
805 gc.setColSpan(cc.gridWidth);
806 gc.setRowSpan(cc.gridHeight);
809 public int getAlignment(RadComponent component, boolean horizontal) {
810 CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
811 CellConstraints.Alignment al = horizontal ? cc.hAlign : cc.vAlign;
812 if (al == CellConstraints.DEFAULT) {
813 FormLayout formLayout = (FormLayout) component.getParent().getLayout();
814 FormSpec formSpec = horizontal
815 ? formLayout.getColumnSpec(component.getConstraints().getColumn()+1)
816 : formLayout.getRowSpec(component.getConstraints().getRow()+1);
817 final FormSpec.DefaultAlignment defaultAlignment = formSpec.getDefaultAlignment();
818 if (defaultAlignment.equals(RowSpec.FILL)) {
819 return GridConstraints.ALIGN_FILL;
821 if (defaultAlignment.equals(RowSpec.TOP) || defaultAlignment.equals(ColumnSpec.LEFT)) {
822 return GridConstraints.ALIGN_LEFT;
824 if (defaultAlignment.equals(RowSpec.CENTER)) {
825 return GridConstraints.ALIGN_CENTER;
827 return GridConstraints.ALIGN_RIGHT;
829 return Utils.alignFromConstraints(component.getConstraints(), horizontal);
832 public void setAlignment(RadComponent component, boolean horizontal, int alignment) {
833 CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
834 if (horizontal) {
835 cc.hAlign = FormLayoutSerializer.ourHorizontalAlignments [alignment];
837 else {
838 cc.vAlign = FormLayoutSerializer.ourVerticalAlignments [alignment];
840 updateConstraints(component);
843 public void resetAlignment(RadComponent component, boolean horizontal) {
844 CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
845 if (horizontal) {
846 cc.hAlign = CellConstraints.DEFAULT;
848 else {
849 cc.vAlign = CellConstraints.DEFAULT;
851 updateConstraints(component);
854 public boolean isAlignmentModified(RadComponent component, boolean horizontal) {
855 CellConstraints cc = (CellConstraints) component.getCustomLayoutConstraints();
856 CellConstraints.Alignment al = horizontal ? cc.hAlign : cc.vAlign;
857 return al != CellConstraints.DEFAULT;
860 private static void updateConstraints(final RadComponent component) {
861 FormLayout layout = (FormLayout) component.getParent().getLayout();
862 layout.setConstraints(component.getDelegee(), gridToCellConstraints(component));
863 component.getParent().revalidate();
866 public int getMinCellCount() {
867 return 0;
870 @Override
871 public void createSnapshotLayout(final SnapshotContext context,
872 final JComponent parent,
873 final RadContainer container,
874 final LayoutManager layout) {
875 ColumnSpec[] colSpecs;
876 RowSpec[] rowSpecs;
877 int[][] rowGroups;
878 int[][] columnGroups;
879 try {
880 Method method = layout.getClass().getMethod("getRowCount", ArrayUtil.EMPTY_CLASS_ARRAY);
881 int rowCount = ((Integer)method.invoke(layout, ArrayUtil.EMPTY_OBJECT_ARRAY)).intValue();
882 method = layout.getClass().getMethod("getColumnCount", ArrayUtil.EMPTY_CLASS_ARRAY);
883 int columnCount = ((Integer)method.invoke(layout, ArrayUtil.EMPTY_OBJECT_ARRAY)).intValue();
885 rowSpecs = new RowSpec[rowCount];
886 colSpecs = new ColumnSpec[columnCount];
888 method = layout.getClass().getMethod("getRowSpec", int.class);
889 for (int i = 0; i < rowCount; i++) {
890 rowSpecs[i] = (RowSpec)createSerializedCopy(method.invoke(layout, i + 1));
892 method = layout.getClass().getMethod("getColumnSpec", int.class);
893 for (int i = 0; i < columnCount; i++) {
894 colSpecs[i] = (ColumnSpec)createSerializedCopy(method.invoke(layout, i + 1));
897 method = layout.getClass().getMethod("getRowGroups", ArrayUtil.EMPTY_CLASS_ARRAY);
898 rowGroups = (int[][])method.invoke(layout);
900 method = layout.getClass().getMethod("getColumnGroups", ArrayUtil.EMPTY_CLASS_ARRAY);
901 columnGroups = (int[][])method.invoke(layout);
903 catch (Exception ex) {
904 throw new RuntimeException(ex);
907 final FormLayout formLayout = new FormLayout(colSpecs, rowSpecs);
908 formLayout.setRowGroups(rowGroups);
909 formLayout.setColumnGroups(columnGroups);
910 container.setLayout(formLayout);
913 private static Object createSerializedCopy(final Object original) {
914 // FormLayout may have been loaded with a different classloader, so we need to create a copy through serialization
915 Object copy;
916 try {
917 ByteArrayOutputStream baos = new ByteArrayOutputStream();
918 ObjectOutputStream os = new ObjectOutputStream(baos);
919 try {
920 os.writeObject(original);
922 finally {
923 os.close();
926 InputStream bais = new ByteArrayInputStream(baos.toByteArray());
927 ObjectInputStream is = new ObjectInputStream(bais);
928 try {
929 copy = is.readObject();
931 finally {
932 is.close();
935 catch (Exception e) {
936 throw new RuntimeException(e);
938 return copy;
941 @Override
942 public void addSnapshotComponent(final JComponent parent,
943 final JComponent child,
944 final RadContainer container,
945 final RadComponent component) {
946 CellConstraints cc;
947 try {
948 LayoutManager layout = parent.getLayout();
949 //noinspection HardCodedStringLiteral
950 Method method = layout.getClass().getMethod("getConstraints", Component.class);
951 cc = (CellConstraints)createSerializedCopy(method.invoke(layout, child));
953 catch (Exception ex) {
954 throw new RuntimeException(ex);
956 copyCellToGridConstraints(component.getConstraints(), cc);
957 component.setCustomLayoutConstraints(cc);
958 container.addComponent(component);
961 private static class MyPropertyChangeListener implements PropertyChangeListener {
962 private final RadComponent myComponent;
964 public MyPropertyChangeListener(final RadComponent component) {
965 myComponent = component;
968 public void propertyChange(PropertyChangeEvent evt) {
969 if (evt.getPropertyName().equals(RadComponent.PROP_CONSTRAINTS)) {
970 updateConstraints(myComponent);
975 private static class ComponentInsetsProperty extends AbstractInsetsProperty<RadComponent> {
976 public ComponentInsetsProperty() {
977 super(null, "Insets");
980 public Insets getValue(final RadComponent component) {
981 if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
982 final CellConstraints cellConstraints = (CellConstraints)component.getCustomLayoutConstraints();
983 return cellConstraints.insets;
985 return new Insets(0, 0, 0, 0);
988 protected void setValueImpl(final RadComponent component, final Insets value) throws Exception {
989 if (component.getCustomLayoutConstraints() instanceof CellConstraints) {
990 final CellConstraints cellConstraints = (CellConstraints)component.getCustomLayoutConstraints();
991 cellConstraints.insets = value;
993 FormLayout layout = (FormLayout) component.getParent().getLayout();
994 CellConstraints cc = (CellConstraints)layout.getConstraints(component.getDelegee()).clone();
995 cc.insets = value;
996 layout.setConstraints(component.getDelegee(), cc);