Create Custom Widgets using InkScape and NetBeans Visual Library

This blog entry shows one how to create a special widget using InkScape and NetBeans Visual Library. This is part of my effort to create a Java-based implementation of MIT’s Scratch, which is implemented in Squeak Smalltalk.

Step 1 Create JScratch Block Graphics:

First I created the image using Inkscape (steps to follow), import the image to IDE, then create two LayerWidgets, one transparent, and another opaque. Add the image to the background LayerWidget, adn the Swing component to the front LayerWidget. Finally, use the BorderFactory.createEmptyBorder function to limit the size of the swing component at the front layer.

To create the Scratch blocks, I used InkScape, the Open Source SVG editor. Here are the steps I took to create the graphic:

1) Turn on Grid and Guide by selecting View->Grid, and View->Guide, respectively.

2) Use “Bezier Curve and Straight Line” tool to create outline

3) Use “Edit Path by Nodes” tool to edit the outline

4) Then fill the color and add the text. This is what the “Move … Steps” block look like. I pick this block to explain because this is the more complicated block than those which do not have an input field.

Step 2 Create JScratch Block Graphics:

Next, we need to use turn this graphic into a visual widget. To do so, I use NetBeans’ Visual Library API. To learn more on Visual Library API, go back to Step 5 and install the example code. For now, I am just going to assume that you have some understanding of how Visual Library API works.

How to create a widget that looks like a Scratch block.
Let’s focus on the input field, the empty box. Each cell in the grid is 10 by 10 pixel. So there are 60 pixels from the left edge of the block to the left edge of the input box, 60 pixels from the right edge of the block to the right edge of the input box.

In the same way, there are 10 pixels from the top edge of the block to the top edge of the input box, and 20 pixels from the bottom edge of the block to the bottom edge of the input box.

This code is from the JButtonWidgetTest class, included in the test.swing package in the Visual Library API sample code

--------------------------------------------------------------------
public  class JButtonWidgetTest extends Widget {

  private JButton  button = new JButton ();

  public JButtonWidgetTest (Scene  scene) {
    super (scene);
  }
  public JButton  getButton () {
    return button;
  }
  protected  Rectangle calculateClientArea () {
    return new Rectangle  (button.getPreferredSize());
  }

  protected void  paintWidget () {
    button.setSize (getBounds ().getSize ());
    button.paint (getGraphics ());
  }

  public static  void main (String[] args) {
    Scene scene = new Scene ();
    scene.getActions ().addAction (ActionFactory.createZoomAction  ());

    JButtonWidgetTest button = new JButtonWidgetTest  (scene);
    button.getButton ().setText ("My Button");
    scene.addChild (button);

    SceneSupport.show (scene);
  }

}

To run this sample, just right click the JButtonWidgetTest.java in the Project panel and select “Run File”.

The result looks just like a regular JButton, but it’s a Widget that contains a JButton.

This is the simplest way of turning a Swing component to a Widget, but it can only contain one component. To do more, use ComponentWidget. To see ComponentWidget working in action, run ComponentTest.java (right-click and select “Run File”).

The result is a widget that contains a shell that says “Drag this ..” and a swing component.

This is how to create a ComponentWidget instance. ComponentWidget componentWidget = new ComponentWidget (scene, new JComboBox (new String[] { “First”, “Second”, “Third” })); For Scratch blocks, we can use ComponentWidget class to wrap a custom Swing component. To aid the creation of more complex blocks, I added an enum class called ScratchWidgetAttribute.

The ScratchPanelWidet class is very simple and its constructor takes a ScratchWidgetAttribute argument.

public enum ScratchWidgetAttribute {
  MOVE_STEPS("text", 10, 60, 20, 60 ),//top, left, bottom, right
  TURN_CLOCK("turn-clockwise",10, 60, 20, 60);
  private String imagePath;
  private int emptyBoxTop, emptyBoxLeft,  emptyBoxBottom, emptyBoxRight;

  ScratchWidgetAttribute(String image, int top, int left, int bottom, int  right){
    imagePath = image;
    emptyBoxTop = top;
    emptyBoxLeft = left;
    emptyBoxRight = right;
    emptyBoxBottom = bottom;
  }
  public String imageName(){ return  imagePath; }
  public int emptyBoxTop(){ return emptyBoxTop;}
  public int emptyBoxLeft() { return emptyBoxLeft;}
  public int emptyBoxRight(){ return emptyBoxRight;}
  public int emptyBoxBottom(){ return emptyBoxBottom; }

The ScratchPanelFactory is the singleton class that handle creating the complex Swing component that looks like a Scratch block. It’s quite straightforward to create widgets for those blocks that do not contain an input field, such as these:

But it’s a bit tricky when it comes to these blocks:

To do so, first create a transparent JPanel, then add a JTextField to this JPanel. Then wrap this foreground JPanel with a background JPanel. The background JPanel contains the Scratch block image we created in Step 2.1

The code to create this widget is inside ScratchWidgetFactory class. The createScratchComponent method first creates a foreground JPanel, then pass this foreground JPanel to another method, wrapInBackgroundImage, which creates a background JPanel and wraps the foreground JPanel in it, then return the background JPanel.
public class ScratchWidgetFactory {
  ...
  public static JPanel  wrapInBackgroundImage(
    JComponent component, Icon backgroundIcon, int verticalAlignment, int horizontalAlignment)
  {

    // make the passed in  swing component transparent
    component.setOpaque(false);
    component.setBackground (Color.WHITE);
    // create  wrapper JPanel
    JPanel backgroundPanel = new JPanel(new  GridBagLayout());

    // add the passed in swing component  first to ensure that it is in front
    backgroundPanel.add(component, gbc);

    // create a label to  paint the background image
    JLabel backgroundImage = new  JLabel(backgroundIcon);

    // Set the size of both  foreground and background to be the size of the icon
    backgroundImage.setPreferredSize(new  Dimension(backgroundIcon.getIconWidth(),  backgroundIcon.getIconHeight()));
    backgroundImage.setMinimumSize(new  Dimension(backgroundIcon.getIconWidth(),  backgroundIcon.getIconHeight()));

    // align the image  as specified.
    backgroundImage.setVerticalAlignment(verticalAlignment);
    backgroundImage.setHorizontalAlignment(horizontalAlignment);

    // add the background  label
    backgroundPanel.add(backgroundImage, gbc);
    backgroundPanel.setOpaque(false);

    // return the wrapper
    return backgroundPanel;
  }

  public static JPanel  createScratchComponent(ScratchWidgetAttribute attribute) {

    JPanel foregroundPanel = new JPanel(new BorderLayout(0, 0));

    foregroundPanel.setBorder( BorderFactory.createEmptyBorder(
      attribute.emptyBoxTop(), // top
      attribute.emptyBoxLeft(),// left
      attribute.emptyBoxBottom(),// bottom
      attribute.emptyBoxRight() // right
  ));

  foregroundPanel.add(new JLabel(""), BorderLayout.NORTH);
  JTextField textfield = new JTextField(0);
  textfield.setBounds(10, 60, 20, 60);

  foregroundPanel.add(textfield);
  return  wrapInBackgroundImage(foregroundPanel, new ImageIcon(
    ScratchPanelFactory.class.getResource("images/" +  attribute.imageName() + ".png")));
  }
  ...
}

Finally, the test code:

public class ScratchWidgetDemo {

public static void main  (String[] args) {
  Scene scene = new Scene ();

  LayerWidget layer = new LayerWidget (scene);
  scene.addChild(layer);

  Widget scratch1 = new  ScratchPanelWidget(scene,ScratchWidgetAttribute.MOVE_STEPS );
  layer.addChild(scratch1);

  Widget turnClock = new  ScratchPanelWidget(scene,ScratchWidgetAttribute.TURN_CLOCK );
  layer.addChild(turnClock);

  scene.getActions().addAction (ActionFactory.createZoomAction ());
  scene.getActions().addAction (ActionFactory.createPanAction ());

  SceneSupport.show (scene.createView ());
}

public  class SceneSupport {
  ...
  private static void showEDT  (Scene scene) {
    JComponent sceneView = scene.getView ();
    if (sceneView == null)
      sceneView =  scene.createView ();
    show (sceneView);
}

public static void show (final JComponent sceneView) {
  if  (SwingUtilities.isEventDispatchThread ()){
    showEDT  (sceneView);
  }
  else {
    SwingUtilities.invokeLater  (new Runnable() {
    public void run () {
      showEDT (sceneView);
    }
};
}
...
}

Download the complete source code bundle here.

10 Comments

  1. Thanks for that tutorial!

    I would be very interested with downloading that source code bundle but unfortunately it is not available anymore.

    Can you upload it another time, or maybe send them directly to me, I would be really gratefull.

    Thanks in advance!

  2. Thanks for that tutorial!
    I would be very interested with downloading that source code bundle but unfortunately it is not available anymore.

    Can you upload and send “source code bundle “directly to me, you can send it to my e-mail “phani2431@gmail.com” .I would be really gratefull.

  3. Ismail

    Thanks for that tutorial!
    I would be very interested with downloading that source code bundle but unfortunately it is not available anymore.

    • Jessica Chiang

      The source code for this project is very rudimentary. It might have been removed from Google Drive by mistake. If you are interested. I will try dig it out of my machine and put it under the download page

      • alane

        Hallo Jessica,
        please i don´t found the link to download the projekt under the Download page. could you explain me exatly,where it is.

        • Jessica Chiang

          The link (at the end of the article) works fine for me. Try again?

Leave a Reply