@Override
  public void drag(MouseEvent e, Dimension delta) {
    if (invalidGesture) {
      return;
    }

    if (selectionBounds == null) {
      return;
    }

    Rectangle sel = updateSelectionBounds(e);
    for (IContentPart<Node, ? extends Node> targetPart : targetParts) {
      // compute initial and new bounds for this target
      Bounds initialBounds = getBounds(selectionBounds, targetPart);
      Bounds newBounds = getBounds(sel, targetPart);

      // compute translation in scene coordinates
      double dx = newBounds.getMinX() - initialBounds.getMinX();
      double dy = newBounds.getMinY() - initialBounds.getMinY();

      // transform translation to parent coordinates
      Node visual = targetPart.getVisual();
      Point2D originInParent = visual.getParent().sceneToLocal(0, 0);
      Point2D deltaInParent = visual.getParent().sceneToLocal(dx, dy);
      dx = deltaInParent.getX() - originInParent.getX();
      dy = deltaInParent.getY() - originInParent.getY();

      // apply translation
      getTransformPolicy(targetPart).setPostTranslate(translateIndices.get(targetPart), dx, dy);

      // check if we can resize the part
      AffineTransform affineTransform = getTransformPolicy(targetPart).getCurrentNodeTransform();
      if (affineTransform.getRotation().equals(Angle.fromDeg(0))) {
        // no rotation => resize possible
        // TODO: special case 90 degree rotations
        double dw = newBounds.getWidth() - initialBounds.getWidth();
        double dh = newBounds.getHeight() - initialBounds.getHeight();
        Point2D originInLocal = visual.sceneToLocal(newBounds.getMinX(), newBounds.getMinY());
        Point2D dstInLocal =
            visual.sceneToLocal(newBounds.getMinX() + dw, newBounds.getMinY() + dh);
        dw = dstInLocal.getX() - originInLocal.getX();
        dh = dstInLocal.getY() - originInLocal.getY();
        getResizePolicy(targetPart).resize(dw, dh);
      } else {
        // compute scaling based on bounds change
        double sx = newBounds.getWidth() / initialBounds.getWidth();
        double sy = newBounds.getHeight() / initialBounds.getHeight();
        // apply scaling
        getTransformPolicy(targetPart).setPostScale(scaleIndices.get(targetPart), sx, sy);
      }
    }
  }
  @Override
  public IUndoableOperation commit() {
    final IUndoableOperation updateVisualOperation = super.commit();
    if (updateVisualOperation == null) {
      return null;
    }

    // commit changes to model
    final FXGeometricShapePart host = getHost();
    final FXGeometricShape hostContent = host.getContent();

    // determine transformation
    @SuppressWarnings("serial")
    Provider<Affine> affineProvider =
        host.getAdapter(
            AdapterKey.<Provider<? extends Affine>>get(
                new TypeToken<Provider<? extends Affine>>() {},
                FXTransformPolicy.TRANSFORMATION_PROVIDER_ROLE));
    AffineTransform tx = JavaFX2Geometry.toAffineTransform(affineProvider.get());
    final AffineTransform oldTransform = hostContent.getTransform();
    final AffineTransform newTransform =
        new AffineTransform(
            tx.getM00(),
            tx.getM10(),
            tx.getM01(),
            tx.getM11(),
            tx.getTranslateX(),
            tx.getTranslateY());

    // create operation to write the changes to the model
    final IUndoableOperation updateModelOperation =
        new AbstractOperation("Update Model") {

          @Override
          public IStatus execute(IProgressMonitor monitor, IAdaptable info)
              throws ExecutionException {
            hostContent.setTransform(newTransform);
            return Status.OK_STATUS;
          }

          @Override
          public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            return execute(monitor, info);
          }

          @Override
          public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            hostContent.setTransform(oldTransform);
            return Status.OK_STATUS;
          }
        };
    // compose operations
    IUndoableOperation compositeOperation =
        new ForwardUndoCompositeOperation(updateVisualOperation.getLabel()) {
          {
            add(updateVisualOperation);
            add(updateModelOperation);
          }
        };

    return compositeOperation;
  }