Package com.google.gdt.eclipse.designer.model.widgets.panels.grid

Source Code of com.google.gdt.eclipse.designer.model.widgets.panels.grid.HTMLTableInfo$TaggedParameterVisitor

/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.google.gdt.eclipse.designer.model.widgets.panels.grid;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gdt.eclipse.designer.Activator;
import com.google.gdt.eclipse.designer.model.widgets.WidgetInfo;
import com.google.gdt.eclipse.designer.model.widgets.panels.ComplexPanelInfo;
import com.google.gdt.eclipse.designer.model.widgets.panels.PanelClipboardCommand;
import com.google.gdt.eclipse.designer.model.widgets.support.DOMUtils;
import com.google.gdt.eclipse.designer.model.widgets.support.GwtState;

import org.eclipse.wb.core.gef.policy.layout.grid.IGridInfo;
import org.eclipse.wb.core.model.AbstractComponentInfo;
import org.eclipse.wb.core.model.IAbstractComponentInfo;
import org.eclipse.wb.core.model.JavaInfo;
import org.eclipse.wb.core.model.ObjectInfo;
import org.eclipse.wb.core.model.association.AssociationObjects;
import org.eclipse.wb.core.model.broadcast.JavaEventListener;
import org.eclipse.wb.core.model.broadcast.ObjectEventListener;
import org.eclipse.wb.core.model.broadcast.ObjectInfoDelete;
import org.eclipse.wb.draw2d.geometry.Insets;
import org.eclipse.wb.draw2d.geometry.Interval;
import org.eclipse.wb.draw2d.geometry.Rectangle;
import org.eclipse.wb.internal.core.model.JavaInfoEvaluationHelper;
import org.eclipse.wb.internal.core.model.JavaInfoUtils;
import org.eclipse.wb.internal.core.model.clipboard.ClipboardCommand;
import org.eclipse.wb.internal.core.model.creation.ConstructorCreationSupport;
import org.eclipse.wb.internal.core.model.creation.CreationSupport;
import org.eclipse.wb.internal.core.model.creation.ExposedPropertyCreationSupport;
import org.eclipse.wb.internal.core.model.description.AbstractInvocationDescription;
import org.eclipse.wb.internal.core.model.description.ComponentDescription;
import org.eclipse.wb.internal.core.model.description.ConstructorDescription;
import org.eclipse.wb.internal.core.model.description.MethodDescription;
import org.eclipse.wb.internal.core.model.description.ParameterDescription;
import org.eclipse.wb.internal.core.model.property.converter.IntegerConverter;
import org.eclipse.wb.internal.core.utils.ast.AstEditor;
import org.eclipse.wb.internal.core.utils.ast.AstNodeUtils;
import org.eclipse.wb.internal.core.utils.ast.DomGenerics;
import org.eclipse.wb.internal.core.utils.check.Assert;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
import org.eclipse.wb.internal.core.utils.execution.RunnableObjectEx;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;

import org.apache.commons.lang.NotImplementedException;

import java.util.List;
import java.util.Map;

/**
* Model for GWT <code>HTMLTable</code>.
*
* @author scheglov_ke
* @coverage gwt.model
*/
public abstract class HTMLTableInfo extends ComplexPanelInfo {
  private JavaInfo m_rowFormatter;
  private JavaInfo m_cellFormatter;

  ////////////////////////////////////////////////////////////////////////////
  //
  // Constructor
  //
  ////////////////////////////////////////////////////////////////////////////
  public HTMLTableInfo(AstEditor editor,
      ComponentDescription description,
      CreationSupport creationSupport) throws Exception {
    super(editor, description, creationSupport);
    cleanUpAfterChildRemove();
    addContextMenuActions();
    new CellPropertySupport(this);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Images
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @return the {@link Image} for {@link HTMLTableInfo}.
   */
  public static Image getImage(String name) {
    return Activator.getImage("info/HTMLTable/" + name);
  }

  /**
   * @return the {@link ImageDescriptor} for {@link HTMLTableInfo}.
   */
  public static ImageDescriptor getImageDescriptor(String name) {
    return Activator.getImageDescriptor("info/HTMLTable/" + name);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Events
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * When {@link WidgetInfo} is removed from {@link HTMLTableInfo} we should remove its
   * {@link CellConstraintsSupport} and empty trailing columns/rows.
   */
  private void cleanUpAfterChildRemove() {
    addBroadcastListener(new ObjectInfoDelete() {
      @Override
      public void before(ObjectInfo parent, ObjectInfo child) throws Exception {
        if (child instanceof WidgetInfo && parent == HTMLTableInfo.this) {
          cleanUpWidget(child);
        }
      }
    });
    addBroadcastListener(new JavaEventListener() {
      @Override
      public void moveBefore(JavaInfo child, ObjectInfo oldParent, JavaInfo newParent)
          throws Exception {
        if (child instanceof WidgetInfo
            && oldParent == HTMLTableInfo.this
            && newParent != oldParent) {
          cleanUpWidget(child);
        }
      }
    });
    addBroadcastListener(new ObjectEventListener() {
      @Override
      public void childRemoveAfter(ObjectInfo parent, ObjectInfo child) throws Exception {
        if (child instanceof WidgetInfo && parent == HTMLTableInfo.this) {
          // remove setX() invocations for widget cell
          {
            CellConstraintsSupport constraints = m_constraints.get(child);
            removeCellInvocations(new Rectangle(constraints.getX(), constraints.getY(), 1, 1));
          }
          // we don't need constraints anymore
          m_constraints.remove(child);
          // delete trailing dimensions
          deleteTrailingEmptyColumns();
          deleteTrailingEmptyRows();
        }
      }
    });
  }

  private void cleanUpWidget(ObjectInfo child) throws Exception {
    WidgetInfo widget = (WidgetInfo) child;
    setComponentColSpan(widget, 1);
    setComponentRowSpan(widget, 1);
  }

  /**
   * Adds {@link HTMLTableInfo} actions into context menu.
   */
  private void addContextMenuActions() {
    addBroadcastListener(new ObjectEventListener() {
      @Override
      public void addContextMenu(List<? extends ObjectInfo> objects,
          ObjectInfo object,
          IMenuManager manager) throws Exception {
        if (object instanceof WidgetInfo && object.getParent() == HTMLTableInfo.this) {
          WidgetInfo component = (WidgetInfo) object;
          CellConstraintsSupport support = getConstraints(component);
          support.addContextMenu(manager);
        }
      }
    });
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Exposed objects
  //
  ////////////////////////////////////////////////////////////////////////////
  @Override
  public void createExposedChildren() throws Exception {
    super.createExposedChildren();
    if (m_rowFormatter == null) {
      m_rowFormatter = JavaInfoUtils.addChildExposedByMethod(this, "getRowFormatter");
    }
    JavaInfoUtils.addChildExposedByMethod(this, "getColumnFormatter");
    if (m_cellFormatter == null) {
      m_cellFormatter = JavaInfoUtils.addChildExposedByMethod(this, "getCellFormatter");
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Refresh
  //
  ////////////////////////////////////////////////////////////////////////////
  @Override
  public void refresh_dispose() throws Exception {
    m_gridInfo = null;
    super.refresh_dispose();
  }

  @Override
  protected void refresh_afterCreate0() throws Exception {
    if (!isPlaceholder()) {
      initializeTableStatus();
    }
    super.refresh_afterCreate0();
  }

  @Override
  protected void refresh_fetch() throws Exception {
    super.refresh_fetch();
    // ask constraints for all components now,
    // to avoid their lazy initialization later (may be at time when we temporary inconsistent)
    if (!isPlaceholder()) {
      for (WidgetInfo component : getChildrenWidgets()) {
        getConstraints(component);
      }
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Internal access
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @return the <code>rowFormatter</code> exposed object.
   */
  JavaInfo getRowFormatter() {
    return m_rowFormatter;
  }

  /**
   * @return the <code>cellFormatter</code> exposed object.
   */
  JavaInfo getCellFormatter() {
    return m_cellFormatter;
  }

  /**
   * @return the {@link HTMLTableStatus} to access/update low-level information.
   */
  public HTMLTableStatus getStatus() {
    return m_status;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Invocations visitor
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Visits <code>setX(row, cell, value)</code> invocations.
   */
  interface CellInvocationsVisitor {
    void visit(MethodDescription methodDescription,
        MethodInvocation invocation,
        Expression rowArgument,
        Expression cellArgument,
        int row,
        int cell) throws Exception;
  }

  /**
   * Visits <code>setX(row, cell, value)</code> invocations of given this {@link HTMLTableInfo} and
   * its formatters.
   */
  protected final void visitCellInvocations(CellInvocationsVisitor visitor) throws Exception {
    visitCellInvocations(this, visitor);
    for (JavaInfo child : getChildrenJava()) {
      if (child.getCreationSupport() instanceof ExposedPropertyCreationSupport) {
        visitCellInvocations(child, visitor);
      }
    }
  }

  /**
   * Visits <code>setX(row, cell, value)</code> invocations of given this {@link HTMLTableInfo} and
   * its formatters.
   */
  protected final void removeCellInvocations(final Rectangle cells) throws Exception {
    visitCellInvocations(new CellInvocationsVisitor() {
      public void visit(MethodDescription methodDescription,
          MethodInvocation invocation,
          Expression rowArgument,
          Expression cellArgument,
          int row,
          int cell) throws Exception {
        int column = getStatus().getColumnOfCell(row, cell);
        if (cells.y <= row && row < cells.bottom() && cells.x <= column && column < cells.right()) {
          getEditor().removeEnclosingStatement(invocation);
        }
      }
    });
  }

  /**
   * Removes <code>setX(row, cell, value)</code> invocations in given cell with given signature.
   */
  protected final void removeCellInvocations(final int sourceRow,
      final int sourceCell,
      final String sourceSignature) throws Exception {
    visitCellInvocations(new CellInvocationsVisitor() {
      public void visit(MethodDescription methodDescription,
          MethodInvocation invocation,
          Expression rowArgument,
          Expression cellArgument,
          int row,
          int cell) throws Exception {
        if (row == sourceRow
            && cell == sourceCell
            && AstNodeUtils.getMethodSignature(invocation).equals(sourceSignature)) {
          getEditor().removeEnclosingStatement(invocation);
        }
      }
    });
  }

  /**
   * Visits <code>setX(row, cell, value)</code> invocations of given {@link JavaInfo}.
   */
  private static void visitCellInvocations(JavaInfo javaInfo, CellInvocationsVisitor visitor)
      throws Exception {
    for (ASTNode node : javaInfo.getRelatedNodes()) {
      MethodInvocation invocation = javaInfo.getMethodInvocation(node);
      if (invocation != null) {
        String methodSignature = AstNodeUtils.getMethodSignature(invocation);
        MethodDescription methodDescription = javaInfo.getDescription().getMethod(methodSignature);
        if (methodDescription != null
            && methodDescription.getName().startsWith("set")
            && methodDescription.getParameters().size() > 2
            && methodDescription.getParameter(0).getType() == int.class
            && methodDescription.getParameter(1).getType() == int.class
            && methodDescription.getParameter(0).getTags().containsKey("HTMLTable.row")
            && methodDescription.getParameter(1).getTags().containsKey("HTMLTable.cell")) {
          List<Expression> arguments = DomGenerics.arguments(invocation);
          Expression rowExpression = arguments.get(0);
          Expression cellExpression = arguments.get(1);
          int row = (Integer) JavaInfoEvaluationHelper.getValue(rowExpression);
          int cell = (Integer) JavaInfoEvaluationHelper.getValue(cellExpression);
          visitor.visit(methodDescription, invocation, rowExpression, cellExpression, row, cell);
        }
      }
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // AST utils
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Visits arguments for {@link MethodInvocation} parameters marked with some tag.
   */
  interface TaggedParameterVisitor {
    void visit(Expression argument) throws Exception;
  }
  /**
   * Visits arguments for {@link MethodInvocation} parameters marked with some tag.
   */
  interface TaggedParameterVisitorEx {
    void visit(AbstractInvocationDescription methodDescription,
        List<Expression> arguments,
        ParameterDescription parameter,
        Expression argument) throws Exception;
  }

  /**
   * Visits arguments of {@link MethodInvocation} which {@link ParameterDescription} is marked with
   * required tag. The {@link MethodInvocation}'s of this {@link HTMLTableInfo} and its exposed
   * formatters are considered.
   */
  void visitTaggedParameters(String tagName, final TaggedParameterVisitor visitor) throws Exception {
    TaggedParameterVisitorEx _visitor = new TaggedParameterVisitorEx() {
      public void visit(AbstractInvocationDescription methodDescription,
          List<Expression> arguments,
          ParameterDescription parameter,
          Expression argument) throws Exception {
        visitor.visit(argument);
      }
    };
    visitTaggedParameters(tagName, _visitor);
  }

  /**
   * Visits arguments of {@link MethodInvocation} which {@link ParameterDescription} is marked with
   * required tag. The {@link MethodInvocation}'s of this {@link HTMLTableInfo} and its exposed
   * formatters are considered.
   */
  void visitTaggedParameters(String tagName, TaggedParameterVisitorEx visitor) throws Exception {
    visitTaggedParameters(this, visitor, tagName);
    for (JavaInfo child : getChildrenJava()) {
      if (child.getCreationSupport() instanceof ExposedPropertyCreationSupport) {
        visitTaggedParameters(child, visitor, tagName);
      }
    }
  }

  /**
   * Visits arguments of {@link MethodInvocation} of given {@link JavaInfo} which
   * {@link ParameterDescription} is marked with required tag.
   */
  private static void visitTaggedParameters(JavaInfo javaInfo,
      TaggedParameterVisitorEx visitor,
      String tagName) throws Exception {
    // visit constructor
    if (javaInfo.getCreationSupport() instanceof ConstructorCreationSupport) {
      ConstructorCreationSupport creationSupport =
          (ConstructorCreationSupport) javaInfo.getCreationSupport();
      ClassInstanceCreation creation = creationSupport.getCreation();
      ConstructorDescription constructorDescription = creationSupport.getDescription();
      List<Expression> arguments = DomGenerics.arguments(creation);
      for (ParameterDescription parameter : constructorDescription.getParameters()) {
        if (parameter.hasTrueTag(tagName)) {
          visitTaggedParameter(visitor, constructorDescription, arguments, parameter);
        }
      }
    }
    // visit methods
    for (MethodInvocation invocation : javaInfo.getMethodInvocations()) {
      String methodSignature = AstNodeUtils.getMethodSignature(invocation);
      MethodDescription methodDescription = javaInfo.getDescription().getMethod(methodSignature);
      if (methodDescription != null) {
        List<Expression> arguments = DomGenerics.arguments(invocation);
        for (ParameterDescription parameter : methodDescription.getParameters()) {
          if (parameter.hasTrueTag(tagName)) {
            visitTaggedParameter(visitor, methodDescription, arguments, parameter);
          }
        }
      }
    }
  }

  private static void visitTaggedParameter(TaggedParameterVisitorEx visitor,
      AbstractInvocationDescription description,
      List<Expression> arguments,
      ParameterDescription parameter) throws Exception {
    int index = parameter.getIndex();
    Expression argument = arguments.get(index);
    visitor.visit(description, arguments, parameter, argument);
  }

  /**
   * Replaces {@link Expression} with integer value.
   */
  void setIntExpression(Expression expression, int value) throws Exception {
    String source = IntegerConverter.INSTANCE.toJavaSource(this, value);
    Expression newExpression = getEditor().replaceExpression(expression, source);
    JavaInfoEvaluationHelper.setValue(newExpression, value);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Table status
  //
  ////////////////////////////////////////////////////////////////////////////
  protected HTMLTableStatus m_status;

  /**
   * Initializes {@link HTMLTableStatus} about underlying <code>TABLE</code> in this model.
   */
  private void initializeTableStatus() throws Exception {
    if (m_status == null) {
      m_status = createTableStatus();
      initializeDimensions();
    }
  }

  /**
   * Creates {@link HTMLTableStatus} that corresponds to the type of {@link HTMLTableInfo}.
   */
  protected abstract HTMLTableStatus createTableStatus() throws Exception;

  ////////////////////////////////////////////////////////////////////////////
  //
  // CellConstraintsSupport access
  //
  ////////////////////////////////////////////////////////////////////////////
  private final Map<WidgetInfo, CellConstraintsSupport> m_constraints = Maps.newHashMap();

  /**
   * @return the {@link CellConstraintsSupport} for given {@link ComponentInfo}.
   */
  public static CellConstraintsSupport getConstraints(final WidgetInfo component) {
    return ExecutionUtils.runObject(new RunnableObjectEx<CellConstraintsSupport>() {
      public CellConstraintsSupport runObject() throws Exception {
        HTMLTableInfo panel = (HTMLTableInfo) component.getParent();
        Assert.isTrue(panel.getChildrenWidgets().contains(component));
        //
        CellConstraintsSupport cell = panel.m_constraints.get(component);
        if (cell == null) {
          cell = new CellConstraintsSupport(panel, component);
          panel.m_constraints.put(component, cell);
        }
        return cell;
      }
    });
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Dimensions
  //
  ////////////////////////////////////////////////////////////////////////////
  private int m_deleteDimensionLevel = 0;
  private final List<ColumnInfo> m_columns = Lists.newArrayList();
  private final List<RowInfo> m_rows = Lists.newArrayList();

  /**
   * Fills {@link #m_columns} and {@link #m_rows} collections.
   */
  private void initializeDimensions() {
    // add columns
    for (int i = 0; i < m_status.getColumnCount(); i++) {
      m_columns.add(new ColumnInfo(this));
    }
    // add rows
    for (int i = 0; i < m_status.getRowCount(); i++) {
      m_rows.add(new RowInfo(this));
    }
  }

  /**
   * @return the list of all {@link ColumnInfo}'s.
   */
  public final List<ColumnInfo> getColumns() {
    return m_columns;
  }

  /**
   * @return the list of all {@link RowInfo}'s.
   */
  public final List<RowInfo> getRows() {
    return m_rows;
  }

  /**
   * @return <code>true</code> if given column is empty, i.e. has no components that start in it.
   */
  boolean isEmptyColumn(final int column) {
    final boolean[] isEmpty = new boolean[]{true};
    ExecutionUtils.runLog(new RunnableEx() {
      public void run() throws Exception {
        visitTaggedParameters("HTMLTable.cell", new TaggedParameterVisitorEx() {
          public void visit(AbstractInvocationDescription methodDescription,
              List<Expression> arguments,
              ParameterDescription parameter,
              Expression argument) throws Exception {
            int parameterIndex = parameter.getIndex();
            Expression rowExpression = arguments.get(parameterIndex - 1);
            Expression cellExpression = arguments.get(parameterIndex);
            int row = (Integer) JavaInfoEvaluationHelper.getValue(rowExpression);
            int cell = (Integer) JavaInfoEvaluationHelper.getValue(cellExpression);
            int cellColumn = getStatus().getColumnOfCell(row, cell);
            isEmpty[0] &= cellColumn != column;
          }
        });
      }
    });
    return isEmpty[0];
  }

  /**
   * @return <code>true</code> if given row is empty, i.e. has no components that start in it.
   */
  boolean isEmptyRow(final int row) {
    final boolean[] isEmpty = new boolean[]{true};
    ExecutionUtils.runLog(new RunnableEx() {
      public void run() throws Exception {
        visitTaggedParameters("HTMLTable.row", new TaggedParameterVisitorEx() {
          public void visit(AbstractInvocationDescription methodDescription,
              List<Expression> arguments,
              ParameterDescription parameter,
              Expression argument) throws Exception {
            int invocationRow = (Integer) JavaInfoEvaluationHelper.getValue(argument);
            isEmpty[0] &= invocationRow != row;
          }
        });
      }
    });
    return isEmpty[0];
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Column commands
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Inserts new {@link ColumnInfo} into target index.
   */
  public final void insertColumn(final int index) throws Exception {
    m_columns.add(index, new ColumnInfo(this));
    m_status.insertColumn(index);
    // update constraints
    visitGridComponents(new MigComponentVisitor() {
      public void visit(WidgetInfo component, CellConstraintsSupport cell) throws Exception {
        if (cell.getX() >= index) {
          cell.updateX(1);
        } else if (cell.getX() + cell.getWidth() > index) {
          cell.updateWidth(1);
        }
      }
    });
    // update invocations
    visitTaggedParameters("HTMLTable.column", new TaggedParameterVisitor() {
      public void visit(Expression argument) throws Exception {
        int value = (Integer) JavaInfoEvaluationHelper.getValue(argument);
        if (value >= index) {
          setIntExpression(argument, value + 1);
        }
      }
    });
    visitTaggedParameters("HTMLTable.cell", new TaggedParameterVisitorEx() {
      public void visit(AbstractInvocationDescription methodDescription,
          List<Expression> arguments,
          ParameterDescription parameter,
          Expression argument) throws Exception {
        int parameterIndex = parameter.getIndex();
        int row = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(parameterIndex - 1));
        int cell = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(parameterIndex));
        int cellColumn = getStatus().getColumnOfCell(row, cell);
        if (cellColumn >= index) {
          setIntExpression(argument, cell + 1);
        } else if (methodDescription.getSignature().equals("setColSpan(int,int,int)")
            && cellColumn + getStatus().getColSpan(row, cell) > index) {
          int colSpan = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(2));
          setIntExpression(arguments.get(2), colSpan + 1);
        }
      }
    });
  }

  /**
   * Deletes the {@link ColumnInfo} with given index.
   */
  public final void deleteColumn(final int index) throws Exception {
    try {
      m_deleteDimensionLevel++;
      deleteColumn0(index);
    } finally {
      m_deleteDimensionLevel--;
    }
    deleteTrailingEmptyRows();
  }

  /**
   * Implementation of {@link #deleteColumn(int)}.
   */
  private void deleteColumn0(final int index) throws Exception {
    // update constraints
    visitGridComponents(new MigComponentVisitor() {
      public void visit(WidgetInfo component, CellConstraintsSupport cell) throws Exception {
        if (cell.getX() == index) {
          component.delete();
        } else if (cell.getX() >= index) {
          cell.updateX(-1);
        } else if (cell.getX() + cell.getWidth() > index) {
          cell.updateWidth(-1);
        }
      }
    });
    // update invocations
    visitTaggedParameters("HTMLTable.column", new TaggedParameterVisitor() {
      public void visit(Expression argument) throws Exception {
        int value = (Integer) JavaInfoEvaluationHelper.getValue(argument);
        if (value == index) {
          getEditor().removeEnclosingStatement(argument);
        } else if (value >= index) {
          setIntExpression(argument, value - 1);
        }
      }
    });
    visitTaggedParameters("HTMLTable.cell", new TaggedParameterVisitorEx() {
      public void visit(AbstractInvocationDescription methodDescription,
          List<Expression> arguments,
          ParameterDescription parameter,
          Expression argument) throws Exception {
        int parameterIndex = parameter.getIndex();
        int row = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(parameterIndex - 1));
        int cell = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(parameterIndex));
        int cellColumn = getStatus().getColumnOfCell(row, cell);
        if (cellColumn == index) {
          getEditor().removeEnclosingStatement(argument);
        } else if (cellColumn >= index) {
          setIntExpression(argument, cell - 1);
        } else if (methodDescription.getSignature().equals("setColSpan(int,int,int)")
            && cellColumn + getStatus().getColSpan(row, cell) > index) {
          int colSpan = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(2));
          setIntExpression(arguments.get(2), colSpan - 1);
        }
      }
    });
    // update status
    m_columns.remove(index);
    m_status.deleteColumn(index);
    // remove empty rows at the end
    deleteTrailingEmptyRows();
  }

  /**
   * Deletes the {@link ComponentInfo}'s that located in {@link ColumnInfo} with given index.
   */
  public final void clearColumn(final int index) throws Exception {
    // delete components
    visitGridComponents(new MigComponentVisitor() {
      public void visit(WidgetInfo component, CellConstraintsSupport cell) throws Exception {
        if (cell.getX() == index) {
          component.delete();
        }
      }
    });
    // delete invocations
    removeCellInvocations(new Rectangle(index, 0, 1, m_status.getRowCount()));
    // delete trailing dimensions
    deleteTrailingEmptyColumns();
    deleteTrailingEmptyRows();
  }

  /**
   * Deletes empty columns at the end.
   */
  private void deleteTrailingEmptyColumns() throws Exception {
    if (m_deleteDimensionLevel != 0) {
      return;
    }
    while (!getColumns().isEmpty()) {
      int column = getColumns().size() - 1;
      if (isEmptyColumn(column)) {
        deleteColumn(column);
      } else {
        break;
      }
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Row commands
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Inserts new {@link RowInfo} into target index.
   */
  public final void insertRow(final int index) throws Exception {
    m_rows.add(index, new RowInfo(this));
    m_status.insertRow(index);
    // update constraints
    visitGridComponents(new MigComponentVisitor() {
      public void visit(WidgetInfo component, CellConstraintsSupport cell) throws Exception {
        if (cell.getY() >= index) {
          cell.updateY(1);
        } else if (cell.getY() + cell.getHeight() > index) {
          cell.updateHeight(1);
        }
      }
    });
    // update invocations
    visitTaggedParameters("HTMLTable.row", new TaggedParameterVisitorEx() {
      public void visit(AbstractInvocationDescription methodDescription,
          List<Expression> arguments,
          ParameterDescription parameter,
          Expression argument) throws Exception {
        int row = (Integer) JavaInfoEvaluationHelper.getValue(argument);
        if (row >= index) {
          setIntExpression(argument, row + 1);
        } else if (methodDescription.getSignature().equals("setRowSpan(int,int,int)")) {
          int cell = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(1));
          if (row + getStatus().getRowSpan(row, cell) > index) {
            int colSpan = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(2));
            setIntExpression(arguments.get(2), colSpan + 1);
          }
        }
      }
    });
  }

  /**
   * Deletes the {@link RowInfo} with given index.
   */
  public final void deleteRow(final int index) throws Exception {
    try {
      m_deleteDimensionLevel++;
      deleteRow0(index);
    } finally {
      m_deleteDimensionLevel--;
    }
    deleteTrailingEmptyColumns();
  }

  /**
   * Implementation of {@link #deleteRow(int)}.
   */
  private void deleteRow0(final int index) throws Exception {
    // update constraints
    visitGridComponents(new MigComponentVisitor() {
      public void visit(WidgetInfo component, CellConstraintsSupport cell) throws Exception {
        if (cell.getY() == index) {
          component.delete();
        } else if (cell.getY() >= index) {
          cell.updateY(-1);
        } else if (cell.getY() + cell.getHeight() > index) {
          cell.updateHeight(-1);
        }
      }
    });
    // update invocations
    visitTaggedParameters("HTMLTable.row", new TaggedParameterVisitorEx() {
      public void visit(AbstractInvocationDescription methodDescription,
          List<Expression> arguments,
          ParameterDescription parameter,
          Expression argument) throws Exception {
        int row = (Integer) JavaInfoEvaluationHelper.getValue(argument);
        if (row == index) {
          getEditor().removeEnclosingStatement(argument);
        } else if (row > index) {
          setIntExpression(argument, row - 1);
        } else if (methodDescription.getSignature().equals("setRowSpan(int,int,int)")) {
          int cell = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(1));
          if (row + getStatus().getRowSpan(row, cell) > index) {
            int rowSpan = (Integer) JavaInfoEvaluationHelper.getValue(arguments.get(2));
            setIntExpression(arguments.get(2), rowSpan - 1);
          }
        }
      }
    });
    // update status
    m_rows.remove(index);
    m_status.deleteRow(index);
  }

  /**
   * Deletes the {@link ComponentInfo}'s that located in {@link RowInfo} with given index.
   */
  public final void clearRow(final int index) throws Exception {
    // delete components
    visitGridComponents(new MigComponentVisitor() {
      public void visit(WidgetInfo component, CellConstraintsSupport cell) throws Exception {
        if (cell.getY() == index) {
          component.delete();
        }
      }
    });
    // delete invocations
    removeCellInvocations(new Rectangle(0, index, m_status.getColumnCount(), 1));
    // delete trailing dimensions
    deleteTrailingEmptyColumns();
    deleteTrailingEmptyRows();
  }

  /**
   * Deletes empty rows at the end.
   */
  private void deleteTrailingEmptyRows() throws Exception {
    if (m_deleteDimensionLevel != 0) {
      return;
    }
    while (!getRows().isEmpty()) {
      int row = getRows().size() - 1;
      if (isEmptyRow(row)) {
        deleteRow(row);
      } else {
        break;
      }
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Commands
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Sets location of component.
   */
  public final void setComponentLocation(WidgetInfo component, int newColumn, final int newRow)
      throws Exception {
    CellConstraintsSupport constraints = getConstraints(component);
    int column = constraints.getX();
    final int row = constraints.getY();
    if (column == newColumn && row == newRow) {
      return;
    }
    // set location in constraints
    constraints.setX(newColumn);
    constraints.setY(newRow);
    // update setXXX() invocations
    final int newCell = getStatus().getCellOfColumn(newRow, newColumn);
    boolean isExistingCell = row != -1 && column != -1;
    if (isExistingCell) {
      boolean isSameCell = newColumn == column && newRow == row;
      if (!isSameCell) {
        final int cell = getStatus().getCellOfColumn(row, column);
        visitCellInvocations(new CellInvocationsVisitor() {
          public void visit(MethodDescription methodDescription,
              MethodInvocation invocation,
              Expression rowArgument,
              Expression cellArgument,
              int row_,
              int cell_) throws Exception {
            // remove invocations for target cell
            if (row_ == newRow && cell_ == newCell) {
              getEditor().removeEnclosingStatement(invocation);
            }
            // move invocations from source to target cell
            if (row_ == row && cell_ == cell) {
              setIntExpression(rowArgument, newRow);
              setIntExpression(cellArgument, newCell);
            }
          }
        });
      }
    } else {
      constraints.setAssociationArgumentInt("HTMLTable.row", newRow);
      constraints.setAssociationArgumentInt("HTMLTable.cell", newCell);
    }
    // clean up
    deleteTrailingEmptyRows();
    deleteTrailingEmptyColumns();
  }

  /**
   * Sets number of columns filled by component.
   */
  public void setComponentColSpan(WidgetInfo component, int newSpan) throws Exception {
  }

  /**
   * Sets number of rows filled by component.
   */
  public void setComponentRowSpan(WidgetInfo component, int newSpan) throws Exception {
  }

  /**
   * Sets cells occupied by given {@link WidgetInfo}.
   */
  public final void command_CELLS(WidgetInfo component, Rectangle cells) throws Exception {
    setComponentColSpan(component, 1);
    setComponentRowSpan(component, 1);
    setComponentLocation(component, cells.x, cells.y);
    setComponentColSpan(component, cells.width);
    setComponentRowSpan(component, cells.height);
  }

  /**
   * Creates new {@link WidgetInfo} in given cell.
   *
   * @param newComponent
   *          the new {@link WidgetInfo} to create.
   * @param column
   *          the column, 0 based.
   * @param row
   *          the row, 0 based.
   */
  public final void command_CREATE(WidgetInfo newComponent,
      int column,
      boolean insertColumn,
      int row,
      boolean insertRow) throws Exception {
    prepareCell(column, insertColumn, row, insertRow);
    // add in components
    {
      WidgetInfo nextComponent = getReference(column, row, null);
      JavaInfoUtils.add(
          newComponent,
          AssociationObjects.invocationChild("%parent%.setWidget(0, 0, %child%)", true),
          this,
          nextComponent);
    }
    // move in grid
    setComponentLocation(newComponent, column, row);
  }

  /**
   * Moves existing {@link WidgetInfo} into new cell.
   */
  public final void command_MOVE(WidgetInfo component,
      int column,
      boolean insertColumn,
      int row,
      boolean insertRow) throws Exception {
    prepareCell(column, insertColumn, row, insertRow);
    // if internal move, clear span
    if (component.getParent() == this) {
      setComponentColSpan(component, 1);
      setComponentRowSpan(component, 1);
    }
    // move in components
    {
      WidgetInfo nextComponent = getReference(column, row, component);
      JavaInfoUtils.move(
          component,
          AssociationObjects.invocationChild("%parent%.setWidget(0, 0, %child%)", false),
          this,
          nextComponent);
    }
    // move in grid
    setComponentLocation(component, column, row);
  }

  /**
   * @return the {@link WidgetInfo} that should be used as reference for adding into given cell.
   *
   * @param exclude
   *          the {@link ComponentInfo} that should not be checked, for example because we move it
   *          now.
   */
  private WidgetInfo getReference(int column, int row, WidgetInfo exclude) throws Exception {
    for (WidgetInfo component : getChildrenWidgets()) {
      if (component != exclude) {
        CellConstraintsSupport constraints = getConstraints(component);
        if (constraints.getY() > row || constraints.getY() == row && constraints.getX() >= column) {
          return component;
        }
      }
    }
    // no reference
    return null;
  }

  /**
   * Prepares cell with given column/row - inserts columns/rows if necessary.
   */
  protected final void prepareCell(int column, boolean insertColumn, int row, boolean insertRow)
      throws Exception {
    Assert.isTrue(column >= 0, "Invalid column %d", column);
    Assert.isTrue(row >= 0, "Invalid row %d", row);
    // insert column/row
    if (insertColumn) {
      insertColumn(column);
    }
    if (insertRow) {
      insertRow(row);
    }
    // append columns/rows
    while (m_columns.size() <= column) {
      insertColumn(m_columns.size());
    }
    while (m_rows.size() <= row) {
      insertRow(m_rows.size());
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Clipboard
  //
  ////////////////////////////////////////////////////////////////////////////
  @Override
  protected void clipboardCopy_addWidgetCommands(WidgetInfo widget, List<ClipboardCommand> commands)
      throws Exception {
    final Rectangle cells = getGridInfo().getComponentCells(widget);
    commands.add(new PanelClipboardCommand<HTMLTableInfo>(widget) {
      private static final long serialVersionUID = 0L;

      @Override
      protected void add(HTMLTableInfo panel, WidgetInfo widget) throws Exception {
        panel.m_deleteDimensionLevel++;
        try {
          panel.command_CREATE(widget, cells.x, false, cells.y, false);
          panel.command_CELLS(widget, cells);
        } finally {
          panel.m_deleteDimensionLevel--;
        }
      }
    });
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Visiting
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Visitor for {@link WidgetInfo} and their {@link CellConstraintsSupport}.
   */
  interface MigComponentVisitor {
    void visit(WidgetInfo component, CellConstraintsSupport constraints) throws Exception;
  }

  /**
   * Visits grid {@link ComponentInfo}'s of this {@link ContainerInfo}.
   */
  void visitGridComponents(final MigComponentVisitor visitor) {
    ExecutionUtils.runRethrow(new RunnableEx() {
      public void run() throws Exception {
        for (WidgetInfo component : getChildrenWidgets()) {
          CellConstraintsSupport cell = getConstraints(component);
          visitor.visit(component, cell);
        }
      }
    });
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // IGridInfo support
  //
  ////////////////////////////////////////////////////////////////////////////
  private IGridInfo m_gridInfo;

  /**
   * @return the {@link IGridInfo} that describes this layout.
   */
  public IGridInfo getGridInfo() {
    if (m_gridInfo == null) {
      ExecutionUtils.runRethrow(new RunnableEx() {
        public void run() throws Exception {
          createGridInfo();
        }
      });
    }
    return m_gridInfo;
  }

  /**
   * Initializes {@link #m_gridInfo}.
   */
  private void createGridInfo() throws Exception {
    fetchCells();
    m_gridInfo = new IGridInfo() {
      ////////////////////////////////////////////////////////////////////////////
      //
      // Dimensions
      //
      ////////////////////////////////////////////////////////////////////////////
      public int getColumnCount() {
        return m_columnIntervals.length;
      }

      public int getRowCount() {
        return m_rowIntervals.length;
      }

      ////////////////////////////////////////////////////////////////////////////
      //
      // Intervals
      //
      ////////////////////////////////////////////////////////////////////////////
      public Interval[] getColumnIntervals() {
        return m_columnIntervals;
      }

      public Interval[] getRowIntervals() {
        return m_rowIntervals;
      }

      ////////////////////////////////////////////////////////////////////////////
      //
      // Cells
      //
      ////////////////////////////////////////////////////////////////////////////
      public Rectangle getComponentCells(IAbstractComponentInfo component) {
        Assert.instanceOf(WidgetInfo.class, component);
        return m_widgetToCells.get(component);
      }

      public Rectangle getCellsRectangle(Rectangle cells) {
        int x = m_columnIntervals[cells.x].begin;
        int y = m_rowIntervals[cells.y].begin;
        if (cells.isEmpty()) {
          return new Rectangle(x, y, 0, 0);
        } else {
          int w = m_columnIntervals[cells.right() - 1].end() - x;
          int h = m_rowIntervals[cells.bottom() - 1].end() - y;
          return new Rectangle(x, y, w + 1, h + 1);
        }
      }

      ////////////////////////////////////////////////////////////////////////////
      //
      // Feedback
      //
      ////////////////////////////////////////////////////////////////////////////
      public boolean isRTL() {
        return false;
      }

      public Insets getInsets() {
        return new Insets();
      }

      ////////////////////////////////////////////////////////////////////////////
      //
      // Virtual columns
      //
      ////////////////////////////////////////////////////////////////////////////
      public boolean hasVirtualColumns() {
        return false;
      }

      public int getVirtualColumnSize() {
        throw new NotImplementedException("HTMLTable does not supports virtual intervals.");
      }

      public int getVirtualColumnGap() {
        throw new NotImplementedException("HTMLTable does not supports virtual intervals.");
      }

      ////////////////////////////////////////////////////////////////////////////
      //
      // Virtual rows
      //
      ////////////////////////////////////////////////////////////////////////////
      public boolean hasVirtualRows() {
        return false;
      }

      public int getVirtualRowSize() {
        throw new NotImplementedException("HTMLTable does not supports virtual intervals.");
      }

      public int getVirtualRowGap() {
        throw new NotImplementedException("HTMLTable does not supports virtual intervals.");
      }

      ////////////////////////////////////////////////////////////////////////////
      //
      // Checks
      //
      ////////////////////////////////////////////////////////////////////////////
      public AbstractComponentInfo getOccupied(int column, int row) {
        for (Map.Entry<WidgetInfo, Rectangle> entry : m_widgetToCells.entrySet()) {
          if (entry.getValue().contains(column, row)) {
            return entry.getKey();
          }
        }
        return null;
      }
    };
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Cells information
  //
  ////////////////////////////////////////////////////////////////////////////
  private final Map<WidgetInfo, Rectangle> m_widgetToCells = Maps.newHashMap();
  private Interval[] m_columnIntervals;
  private Interval[] m_rowIntervals;

  /**
   * Fetch cells information.
   */
  private void fetchCells() throws Exception {
    GwtState state = getState();
    DOMUtils dom = getDOMUtils();
    m_widgetToCells.clear();
    // prepare map: Element -> Widget_Info
    Map<Object, WidgetInfo> elementToWidgetMap = Maps.newHashMap();
    for (WidgetInfo widget : getChildrenWidgets()) {
      Object widgetObject = widget.getObject();
      Object widgetElement = state.getUIObjectUtils().getElement(widgetObject);
      elementToWidgetMap.put(widgetElement, widget);
    }
    // prepare cells used by each Widget_Info
    m_columnIntervals = new Interval[m_status.getColumnCount()];
    m_rowIntervals = new Interval[m_status.getRowCount()];
    Map<Interval, Interval> spannedColumnIntervals = Maps.newHashMap();
    Map<Interval, Interval> spannedRowIntervals = Maps.newHashMap();
    for (int row = 0; row < m_status.getRowCount(); row++) {
      for (int cell = 0, column = 0; column < m_status.getColumnCount(); cell++) {
        // FlexTableHelper may remove cells in row below spanned
        if (!m_status.isExistingCell(row, cell)) {
          column++;
          continue;
        }
        // prepare cell information
        int fixedCell = m_status.fixCellAfterRowSpan(row, cell);
        Object td = m_status.getElement(row, fixedCell);
        int colSpan = m_status.getColSpan(row, cell);
        int rowSpan = m_status.getRowSpan(row, cell);
        // remember widget cells
        if (dom.getChildCount(td) == 1) {
          Object tdChild = dom.getChild(td, 0);
          tdChild = dom.unwrapElement(tdChild);
          WidgetInfo widget = elementToWidgetMap.get(tdChild);
          if (widget != null) {
            m_widgetToCells.put(widget, new Rectangle(column, row, colSpan, rowSpan));
          }
        }
        // prepare row interval
        if (m_rowIntervals[row] == null) {
          Object trElement = dom.getParent(td);
          Rectangle trBounds = state.getAbsoluteBounds(trElement);
          absoluteToRelative(trBounds);
          Interval trInterval = new Interval(trBounds.y, trBounds.height);
          if (rowSpan == 1) {
            m_rowIntervals[row] = trInterval;
          } else {
            Rectangle tdBounds = state.getAbsoluteBounds(td);
            Interval spannedInterval = new Interval(trBounds.y, tdBounds.height);
            spannedRowIntervals.put(new Interval(row, rowSpan), spannedInterval);
          }
        }
        // prepare column interval
        if (m_columnIntervals[column] == null) {
          Rectangle tdBounds = state.getAbsoluteBounds(td);
          absoluteToRelative(tdBounds);
          Interval columnInterval = new Interval(tdBounds.x, tdBounds.width);
          if (colSpan == 1) {
            m_columnIntervals[column] = columnInterval;
          } else {
            spannedColumnIntervals.put(new Interval(column, colSpan), columnInterval);
          }
        }
        // next column
        column += colSpan;
      }
    }
    // fix spanned columns/rows
    fetchCells_fixSpannedColumns(spannedColumnIntervals);
    fetchCells_fixSpannedRows(spannedRowIntervals);
    // if no rows, fill column intervals
    if (m_rowIntervals.length == 0) {
      for (int i = 0; i < m_columnIntervals.length; i++) {
        m_columnIntervals[i] = new Interval();
      }
    }
    // if no columns, fill row intervals
    if (m_columnIntervals.length == 0) {
      for (int i = 0; i < m_rowIntervals.length; i++) {
        m_rowIntervals[i] = new Interval();
      }
    }
    // ensure that widget has at least some cells
    for (WidgetInfo widget : getChildrenWidgets()) {
      Rectangle cells = m_widgetToCells.get(widget);
      if (cells == null) {
        m_widgetToCells.put(widget, new Rectangle(0, 0, 0, 0));
      }
    }
    //
    /*System.out.println(getStatus());
    System.out.println("rows: " + ArrayUtils.toString(m_rowIntervals));
    System.out.println("columns: " + ArrayUtils.toString(m_columnIntervals));
    System.out.println("spannedRowIntervals: " + spannedRowIntervals);
    System.out.println("spannedColumnIntervals: " + spannedColumnIntervals);
    System.out.println(StringUtils.join(m_widgetToCells.entrySet().iterator(), "\n\t"));*/
  }

  /**
   * It is possible that some columns don't have individual widgets in not spanned cells, so we can
   * not get exact columns intervals and have to approximate it.
   */
  private void fetchCells_fixSpannedColumns(Map<Interval, Interval> spannedColumnIntervals) {
    for (int column = 0; column < m_columnIntervals.length; column++) {
      Interval columnInterval = m_columnIntervals[column];
      if (columnInterval == null) {
        for (Map.Entry<Interval, Interval> spanEntry : spannedColumnIntervals.entrySet()) {
          Interval spanRange = spanEntry.getKey();
          if (spanRange.contains(column)) {
            int x = spanEntry.getValue().begin;
            int width = spanEntry.getValue().length / spanRange.length;
            for (int _column = spanRange.begin(); _column < spanRange.end(); _column++) {
              if (_column == column) {
                m_columnIntervals[_column] = new Interval(x, width);
              }
              x += width;
            }
            break;
          }
        }
      }
    }
  }

  /**
   * It is possible that some rows don't have individual widgets in not spanned cells, so we can not
   * get exact row intervals and have to approximate it.
   */
  private void fetchCells_fixSpannedRows(Map<Interval, Interval> spannedRowIntervals) {
    for (int row = 0; row < m_rowIntervals.length; row++) {
      Interval rowInterval = m_rowIntervals[row];
      if (rowInterval == null) {
        for (Map.Entry<Interval, Interval> spanEntry : spannedRowIntervals.entrySet()) {
          Interval spanRange = spanEntry.getKey();
          if (spanRange.contains(row)) {
            int y = spanEntry.getValue().begin;
            int height = spanEntry.getValue().length / spanRange.length;
            for (int _row = spanRange.begin(); _row < spanRange.end(); _row++) {
              if (_row == row) {
                m_rowIntervals[_row] = new Interval(y, height);
              }
              y += height;
            }
            break;
          }
        }
      }
    }
  }
}
TOP

Related Classes of com.google.gdt.eclipse.designer.model.widgets.panels.grid.HTMLTableInfo$TaggedParameterVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.