Eclipse Draw2D Layouts Reference Card

FlowLayout (source)

Lays out the children a row or a column, wrapping the row/column when no more space is available.

GridLayout (source)

Lays out the children in a grid. The name of this layout is deceiving, because while it sounds like you could instantiate this grid to have a number of rows and columns and then set the figures giving for the figure a specific row and column, what the layout actually allows you to do is set the number of columns (rows) and then the figures are added sequentially to the next column (row) and after all columns (rows) are filled, a new row (column) is created. You can set some information on how each element is laid out, like alignment, spanning more than one column, etc.

XYLayout (source)

This layout gives you full control, by making you decide where you want to put every figure. With control comes responsibility (and more code).

BorderLayout (source)

Lays out the figures in 5 locations: Top, Bottom, Left, Right, and Center. Only one figure can be located in each location. The bounds given to each figure are based on the figure’s preferred size. The central figure get’s the space remaining after all other figures are laid out – it can both grow and shrink depending on how much space is left.

ToolbarLayout (source)

Arranges the figures in one column/row. It tries to insert all child figures into its client area, up to reducing them to their minimum size, but not less than this.

StackLayout (source)

The children of the figure are placed one on top of the other, with the first child added located at the bottom.

DelegatingLayout (source)

This layout expects a Locator to be passed as the child figure’s constraint. At layout time, it delegates to the Locator to perform the layout.

Enhanced by Zemanta

Creating an OPM GEF Editor – Part 25: “Smart” Multi-line Text Figure

Previous tutorial: Creating an OPM GEF Editor – Part 24: “Smart” Multi-line Text Figure

One of the problems with visual programming languages is layout – you can stay endless hours just arranging the figures in the diagram without any real changes in the program, just to “make it look better”. This is a complete waste of time. For textual languages, it is a standard practice to have code auto-formatters that format the code every time you save. There are even some extremists who consider badly formatted code as having errors (if you are interested in the subject, there are programs like CheckStyle and Sonar to check coding standards, among others. If you use a good another tool and find it helpful, please leave me a note).

Anyway, my solution to this problem is simple: make the layout part of the language, so that it is not “secondary information”. This means that the developer cannot change the layout without changing the meaning of the program. And this also means that the language UI has to be very smart. For example, an OPP process is displayed as an ellipse, with the name of the process inside the ellipse. This could be shown in a number of ways:

Capture2 Capture1 capture4 Capture3

So the developer has a problem – he has to decide how to resize his ellipse in the “best way” possible (which will definitely be different from the “best way” of all other programmers).

My solution: don’t let the programmer decide (at least not directly), but have each model element decide on its size, based on pre-defined configuration. It took me some days to get this to work, and I had to dig and debug the GEF Logic example a couple of times, but finally I managed. I needed to do the following things: 1. calculate how large is the text; 2. calculate the desired width of the the text using a pre-determined width to height ratio (See my question on ux stackexchange regarding this ration); and 3. calculate the final size of the text after resizing it to the calculated width. The last step is needed because the text may actually end narrower because we only want to have new lines when there is a break in the text (spaces, commas, etc.). Draw2d has a component that knows how to do this (called TextFlow) but I couldn’t manage to find a way to calculate the height… I tried to use getPreferredSize() on the TextFlow after setting its size, bounds, etc, but didn’t work. And then I found it: I had to pass -1 as the desired height to the getPreferredSize() method so it calculated it alone!

So now the solution is simple: calculate the size of the text in one line (using the TextUtilities class). Calculate the area that is used by the text and from this derive the desired width, using the width to height ratio. And last, ask the FlowPage to calculate it’s preferred size using this width and a height of -1. I created a new class that does just this:

package com.vainolo.draw2d.extras;

/*******************************************************************************
 * Copyright (c) 2012 Arieh 'Vainolo' Bibliowicz
 * You can use this code for educational purposes. For any other uses
 * please contact me: vainolo@gmail.com
 *******************************************************************************/

import org.eclipse.draw2d.TextUtilities;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.text.FlowPage;
import org.eclipse.draw2d.text.ParagraphTextLayout;
import org.eclipse.draw2d.text.TextFlow;

import com.vainolo.draw2d.extras.translate.JDraw2dToDraw2dTranslations;
import com.vainolo.jdraw2d.HorizontalAlignment;

/**
 * A figure with a {@link TextFlow} that "smartly" calculates its preferred size,
 * using a provided width to height ratio.
 */
public class SmartLabelFigure extends FlowPage {
  private final TextFlow textFlow;
  private double ratio;

  /**
   * Create a new smart label with the given width to height ratio (width = ratio * height)
   *
   * @param ratio
   *          ratio to use when calculating the smart size of the label.
   */
  public SmartLabelFigure(double ratio) {
    super();
    this.ratio = ratio;

    textFlow = new TextFlow();
    textFlow.setLayoutManager(new ParagraphTextLayout(textFlow, ParagraphTextLayout.WORD_WRAP_HARD));
    add(textFlow);

  }

  public void setText(String text) {
    textFlow.setText(text);
  }

  public String getText() {
    return textFlow.getText();
  }

  public void setRatio(double ratio) {
    this.ratio = ratio;
  }

  public double getRatio() {
    return ratio;
  }

  /**
   * Calculate the best size for this label using the class's width to height ratio.
   * This is done by calculating the area that the text would occupy if it was in only
   * one line, then calculate a new width that would give the same area using the
   * width to height ratio, and finally it sends this width to the {@link FlowPage}'s
   * {@link FlowPage#getPreferredSize(int, int)} method which calculates the real
   * height using line breaks.
   *
   * @return A close match to the size that this figure should have to match
   *         the required width to height ratio.
   */
  public Dimension calculateSize() {
    Dimension lineDimensions = TextUtilities.INSTANCE.getStringExtents(textFlow.getText(), getFont());
    double area = lineDimensions.width() * lineDimensions.height();
    double width = Math.sqrt(area / ratio) * ratio;
    invalidate();
    return getPreferredSize((int) width, -1);
  }

  public void setHorizontalAlignment(HorizontalAlignment hAlignment) {
    setHorizontalAligment(JDraw2dToDraw2dTranslations.translateHorizontalAlignment(hAlignment));
  }
}

(Note: I’m using here some special classes that I am creating in order to port the draw2d project outsize of eclipse. I’ll write
more about this in a future post).

So there it is, now the programmer can’t resize the figures, not matter how hard he tries. And the size is calculated automatically.
Using a ratio of 2, the example above looks something like this:

Capture

And here is another example:

Capture5

The code for this class (and more goodies to come) can be found here and my new jdraw2d library (in progress) can be found here.

Update: the FlowPage stores a kind of “cache” of the last 3 saved layouts, so my implementation didn’t give the correct results sometime. Thankfully,
invalidate() clears this cache. The code was updated to mach this.

Enhanced by Zemanta

Creating an OPM GEF Editor – Part 24: Showing Feedback to the User

Previous tutorial: Creating an OPM GEF Editor – Part 23: Drag & Drop from the Palette

Giving feedback to the user is very important in graphical editors which may have lots of information and we would like to minimize the amount of mistakes the user does. One example of feedback is coloring a figure when it is the target of an “add” operations – for example, color the Object to which a State is being added. The user can see very fast the change in the object and act upon the change (validate/cancel).

This is one of the places where the investment in GEF gives its fruits. Adding feedback seemed so simple that I thought is wouldn’t work. Feedback is handled by the EditPolicy in charge of creating new model elements, in my case, OPMContainerXYLayoutPolicy. Two functions were overriden to provide feedback: showLayoutTargetFeedback and eraseLayoutTargetFeedback. Both of these functions are called automatically by the GEF framework. To show how this works, I decided to paint the target figure a light blue color and to add a Donald Duck icon that moves with the cursor while you can apply the policy. This is the code that implements this functionality:

  @Override
  protected void showLayoutTargetFeedback(Request request) {
    if(getHostFigure() instanceof OPMThingFigure) {
      OPMThingFigure figure = (OPMThingFigure) getHostFigure();
      figure.setBackgroundColor(ColorConstants.lightBlue);
      figure.setOpaque(true);
      // Adding a new ellipse somewhere in the screen:
      if(!request.getExtendedData().containsKey("feedbackFigure")) {
        IFigure feedbackFigure =
            new ImageFigure(ImageDescriptor.createFromFile(this.getClass(), "../icons/dd.png").createImage());
        feedbackFigure.setLocation(((DropRequest) request).getLocation());
        feedbackFigure.setSize(feedbackFigure.getPreferredSize());
        addFeedback(feedbackFigure);
        request.getExtendedData().put("feedbackFigure", feedbackFigure);
      } else {
        IFigure feedbackFigure = (IFigure) request.getExtendedData().remove("feedbackFigure");
        feedbackFigure.setLocation(((DropRequest) request).getLocation());
        request.getExtendedData().put("feedbackFigure", feedbackFigure);
      }
    }
  }

  @Override
  protected void eraseLayoutTargetFeedback(Request request) {
    if(getHostFigure() instanceof OPMThingFigure) {
      OPMThingFigure figure = (OPMThingFigure) getHostFigure();
      figure.setBackgroundColor(ColorConstants.white);
      figure.setOpaque(false);
      IFigure feedbackFigure = (IFigure) request.getExtendedData().remove("feedbackFigure");
      removeFeedback(feedbackFigure);
    }
  }

And this is how it looks (thanks to Screencast-O-Matic):

feedbackvideo

Cool, Ah? As usual, the source code for this example (as part of the whole project) can be found here.

Next Tutorial: Creating an OPM GEF Editor – Part 25: “Smart” Multi-line Text Figure

Enhanced by Zemanta