/** Invoked when an action occurs. */
  public void actionPerformed(final ActionEvent e) {
    final ReportSelectionModel model = getSelectionModel();
    if (model == null) {
      return;
    }
    final Element[] visualElements = model.getSelectedVisualElements();
    if (visualElements.length <= 1) {
      return;
    }
    final Element[] carrier = new Element[1];
    final Element[] objects = ModelUtility.filterParents(visualElements);
    final MassElementStyleUndoEntryBuilder builder = new MassElementStyleUndoEntryBuilder(objects);

    long minY = Long.MAX_VALUE;
    long maxY = Long.MIN_VALUE;
    for (int j = 0; j < objects.length; j++) {
      final Element object = objects[j];
      final CachedLayoutData data = ModelUtility.getCachedLayoutData(object);
      final long y1 = data.getY();
      final long y2 = y1 + data.getHeight();
      if (y2 > maxY) {
        maxY = y2;
      }
      if (y1 < minY) {
        minY = y1;
      }
    }

    final long centerPoint = minY + (maxY - minY) / 2;

    for (int j = 0; j < objects.length; j++) {
      final Element object = objects[j];
      final CachedLayoutData data = ModelUtility.getCachedLayoutData(object);
      final long elementCenter = data.getY() + data.getHeight() / 2;

      final long delta = centerPoint - elementCenter;
      if (delta == 0) {
        continue;
      }

      carrier[0] = object;
      final MoveDragOperation mop =
          new MoveDragOperation(
              carrier, ORIGIN_POINT, EmptySnapModel.INSTANCE, EmptySnapModel.INSTANCE);
      mop.update(new Point2D.Double(0, StrictGeomUtility.toExternalValue(delta)), 1);
      mop.finish();
    }
    final MassElementStyleUndoEntry massElementStyleUndoEntry = builder.finish();
    getActiveContext()
        .getUndo()
        .addChange(
            ActionMessages.getString("AlignMiddleAction.UndoName"), massElementStyleUndoEntry);
  }
    public void nodeChanged(final ReportModelEvent event) {
      final Object element = event.getElement();
      if (element instanceof ReportElement == false) {
        return;
      }

      final ReportElement reportElement = (ReportElement) element;
      final Section band = getRendererRoot().getElement();
      if (ModelUtility.isDescendant(band, reportElement)) {
        rendererRoot.resetBounds();
        CrosstabRenderComponent.this.revalidate();
        CrosstabRenderComponent.this.repaint();
        return;
      }

      if (reportElement instanceof Section) {
        final Section section = (Section) reportElement;
        if (ModelUtility.isDescendant(section, band)) {
          rendererRoot.resetBounds();
          CrosstabRenderComponent.this.revalidate();
          CrosstabRenderComponent.this.repaint();
        }
      }
    }
  public void update(final Point2D normalizedPoint, final double zoomFactor) {
    final SnapPositionsModel horizontalSnapModel = getHorizontalSnapModel();
    final Element[] selectedVisualElements = getSelectedVisualElements();
    final long originPointX = getOriginPointX();
    final long[] elementWidth = getElementWidth();
    final long px = StrictGeomUtility.toInternalValue(normalizedPoint.getX());
    final long dx = px - originPointX;

    for (int i = 0; i < selectedVisualElements.length; i++) {
      final Element element = selectedVisualElements[i];
      if (element instanceof RootLevelBand) {
        continue;
      }
      final ElementStyleSheet styleSheet = element.getStyle();
      final double elementMinWidth =
          styleSheet.getDoubleStyleProperty(ElementStyleKeys.MIN_WIDTH, 0);

      // this is where I want the element on a global scale...
      final long targetWidth = elementWidth[i] + dx;
      final CachedLayoutData data = ModelUtility.getCachedLayoutData(element);
      final long elementX = data.getX();
      final long targetX2 = elementX + targetWidth;

      if (elementMinWidth >= 0) {
        // absolute position; resolving is easy here
        final long snapPosition =
            horizontalSnapModel.getNearestSnapPosition(targetX2, element.getObjectID());
        if (Math.abs(snapPosition - targetX2) > snapThreshold) {
          final long localWidth = Math.max(0, targetX2 - elementX);
          final float position = (float) StrictGeomUtility.toExternalValue(localWidth);
          styleSheet.setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(position));
        } else {
          final long localWidth = Math.max(0, snapPosition - elementX);
          final float position = (float) StrictGeomUtility.toExternalValue(localWidth);
          styleSheet.setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(position));
        }
      } else {
        final Element parent = element.getParentSection();
        final CachedLayoutData parentData = ModelUtility.getCachedLayoutData(parent);

        final long parentBase = parentData.getWidth();
        if (parentBase > 0) {
          // relative position; resolve the percentage against the height of the parent.
          final long snapPosition =
              horizontalSnapModel.getNearestSnapPosition(targetX2, element.getObjectID());
          if (Math.abs(snapPosition - targetX2) > snapThreshold) {
            final long localWidth = Math.max(0, targetX2 - elementX);
            // strict geometry: all values are multiplied by 1000
            // percentages in the engine are represented by floats betwen 0 and 100.
            final long percentage =
                StrictGeomUtility.toInternalValue(localWidth * 100 / parentBase);
            styleSheet.setStyleProperty(
                ElementStyleKeys.MIN_WIDTH,
                new Float(StrictGeomUtility.toExternalValue(-percentage)));
          } else {
            final long localWidth = Math.max(0, snapPosition - elementX);
            // strict geometry: all values are multiplied by 1000
            // percentages in the engine are represented by floats betwen 0 and 100.
            final long percentage =
                StrictGeomUtility.toInternalValue(localWidth * 100 / parentBase);
            styleSheet.setStyleProperty(
                ElementStyleKeys.MIN_WIDTH,
                new Float(StrictGeomUtility.toExternalValue(-percentage)));
          }
        }
      }

      element.notifyNodePropertiesChanged();
    }
  }
 protected boolean isLocalElement(final ReportElement e) {
   return ModelUtility.isDescendant(rendererRoot.getCrosstabGroup(), e);
 }