Thursday, 28 November 2013

Timed JavaFX Dialogs

When I wrote the original MonologFX dialogs for JavaFX, I was just trying to clean up a few things I'd done in my earlier project, DialogFX, that I felt could have been done better. Based upon some excellent feedback and suggestions, I rolled out the update...just as the OpenJFX team was releasing their own dialog code that is destined to be in JavaFX/OpenJFX. :-) As I mentioned in this previous post, rather than just hoard the code, I released it anyway in the hopes others might continue to find it useful - and updated the dialogs in the JFXtras Labs library as well - but didn't really expect there to be much continued interest.



I'm happy to say I was wrong. One of my goals was to make a very simple set of dialogs that worked well in most cases and that just "got out of the developer's way"...tools you didn't have to think about extensively to use in your JavaFX application, just "drop & go". They were never meant to be all things to all people, rather a solid option for most use cases. But...


I've gotten some excellent follow-on requests, and I've explored several of them. While I may never have the time to implement them all - and some wander FAR from the original goals - some just fit. One of those ideas was for timed dialogs.


What is a Timed Dialog, and When Would I Use It?
A few developers pointed out that there are occasions when an app is running unattended and dialogs can either a) stop everything or b) pile up by the droves on the user's desktop. And JFXtras implementer Scott mentioned how nice it would be to have an informational dialog that worked similar to a mail notification, popping up and then disappearing after some pre-determined amount of time. The user should also be able to clear the dialog immediately, of course.


Enter the timed dialog. Drawing inspiration from the aforementioned mail notifications and a sample game by colleague Angela Caicedo, I expanded upon Angela's game-switching example to create a (hopefully pleasing) dialog fade in/out effect. Using the number of seconds specified by the developer (you!) via the methodsetDisplayTime(int displayTime), MonologFX apportions a reasonable percentage of that time to fade in, display, and fade out operations...making user input entirely optional.


What Does it Look Like?


It's much easier to demo than it is to explain, so I created a quick video of a normal dialog, then a timed one, in action. Click here to watch it on YouTube.


Limitations, Caveats, "Keep Off the Grass" Signs
A timed dialog is really best suited for informational dialogs - those where there is no user input required, like the aforementioned mail notifications. If a response is required from a user and a dialog disappears of its own volition, which option should be chosen? The "cancel" option would seem best in some cases, and the "default" in others.


Creating the fade in/out effect required a non-blocking implementation, which meant that it would always return a value immediately...and in the current design, that is "cancel". Which again points to using them as informational dialogs, but not for obtaining user feedback.


So for the foreseeable future, timed dialogs are really focused upon and should be confined to use as informational dialogs. If you need the application user to make a conscious choice prior to proceeding, keep that dialog prominently displayed until you get a response!


Where Can I Get Them?


I've already pushed the code to my Github repository for MonologFX and the JFXtras-Labs 2.2 and JFXtras-Labs 8.0 repos. And if you just want to download a .jar file and kick the tires, I've put a copy in the MonologFX repo's dist folder. Just download the .zip file, unzip it, and run java -jar MonologFX.jar for a quick demo.



Odds & Ends

I also made a few architectural changes to MonologFX this weekend during our first-ever "Thanks For Sharing Informal, International HackFest", and more will be integrated over time. Please stay tuned for more information. :-)

Happy Coding!
Jay Thakkar

Tuesday, 26 November 2013

Simple Calendar for JavaFX 2.0

One of the missing controls in JavaFX 2.0 is a date picker. So I have developed the SimpleCalendar for that purpose. It was also a good practice to learn what you could do with JavaFX. You can download it from http://code.google.com/p/javafx-widgets/downloads/list
The SimpleCalendar is a combination of two components: A popup container for date picking and a button to display the hidden popup container. The container consist of three blocks. The top block displays the month and the year and hosts two arrows to navigate back and forth between months. The center block only displays first letters of the names of the days. The bottom block displays the days of previous, present and next months. The names are retrieved based on default locale. Since the first day of the week is Monday in some locales it is taken into account.


A string bean property (changed to ObjectProperty 28.01.2012) is used to bind the date information. It can be accessible by adding a change listener to the instance of SimpleCalendar.

A Complex Widget : Waterfall Display

A waterfall display (aka Spectrogram) is a moving bitmap image that shows the history of the recorded spectra. As time proceeds, old samples will be scrolled out of view. I think it is a complex widget (at least for me). I developed it to explore the JavaFX capabilities. The problem is that JavaFX Image and ImageView classes are rather simple to perform complex image operations. Therefore I used AWT classes then converted the buffered image to a JavaFX image in PNG format with the PngEncoder which is developed by J. David Eisenberg (PngEncoder).

The waterfall display is refreshed at intervals between 200 and 3200 ms. The intensity data is stored in a byte array. At each refresh time the last line is added to the array, and the image is regenerated in the memory with the timestamps and the overlay texts if exist.


In this process, first the waterfall part and the timestamp part of the image are created as buffered images. The waterfall part contains the byte array which has the intensity data normalized to 0 – 127. The data are colored with an indexed color model spanned between a first color and a second color. Then both parts are combined into a full buffered image on which the texts are drawn. Last operation is the conversion of the buffered image to an PNG format so it can be handled by JavaFX Image class. Whole generation process runs in the background so the mouse movements are not affected. I implemented JavaFX Task class for threading and think it is easy to implement.


An extended cross hairs is added to get the intensity value at desired points. It is also used to add overlay texts on the image. They can be entered by right click.



The source code can be downloaded from : javafx-widgets

The DoubleSlider Control

Sometimes one may need to adjust upper and lower limits of a variable. In this case it is necessary to utilize one control for each limit. However it could be more useful if two controls would have been integrated into one control. With this idea I’ve developed the DoubleSlider Control which has one thumb for each limit, by modifying the code for the Slider Control in OpenJFX project. It works with JavaFX 2.1.


It can be downloaded from : javafx-widgets

I haven’t implemented the keyboard bindings expect “Home” and “End”, because I couldn’t find an efficient way to handle them. The problem is that I couldn’t manage a thumb focused at a time.



import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import org.controlsfx.Sample;
import org.controlsfx.control.RangeSlider;

public class HelloRangeSlider extends Application implements Sample {
    
    public static void main(String[] args) {
        launch(args);
    }
    
    @Override public String getSampleName() {
        return "RangeSlider";
    }
    
    @Override public Node getPanel(Stage stage) {
        VBox root = new VBox(15);
        
        Region horizontalRangeSlider = createHorizontalSlider();
        Region verticalRangeSlider = createVerticalSlider();
        root.getChildren().addAll(horizontalRangeSlider, verticalRangeSlider);
        
        return root;
    }
    
    @Override public void start(Stage stage) {
//        setUserAgentStylesheet(STYLESHEET_CASPIAN);
        stage.setTitle("RangeSlider Demo");

        Scene scene = new Scene((Parent)getPanel(stage), 520, 360);

        stage.setScene(scene);
        stage.show();
    }
    
    Region createHorizontalSlider() {
        final TextField minField = new TextField();
        minField.setPrefColumnCount(5);
        final TextField maxField = new TextField();
        maxField.setPrefColumnCount(5);

        final RangeSlider hSlider = new RangeSlider(0, 100, 10, 90);
        hSlider.setShowTickMarks(true);
        hSlider.setShowTickLabels(true);
        hSlider.setBlockIncrement(10);
        hSlider.setPrefWidth(200);

        minField.setText("" + hSlider.getLowValue());
        maxField.setText("" + hSlider.getHighValue());

        minField.setEditable(false);
        minField.setPromptText("Min");

        maxField.setEditable(false);
        maxField.setPromptText("Max");

        minField.textProperty().bind(hSlider.lowValueProperty().asString("%.2f"));
        maxField.textProperty().bind(hSlider.highValueProperty().asString("%.2f"));

        HBox box = new HBox(10);
        box.getChildren().addAll(minField, hSlider, maxField);
        box.setPadding(new Insets(20,0,0,20));
        box.setFillHeight(false);

        return box;
    }
    
    
    Region createVerticalSlider() {
        final TextField minField = new TextField();
        minField.setPrefColumnCount(5);
        final TextField maxField = new TextField();
        maxField.setPrefColumnCount(5);

        final RangeSlider vSlider = new RangeSlider(0, 200, 30, 150);
        vSlider.setOrientation(Orientation.VERTICAL);
        vSlider.setPrefHeight(200);
        vSlider.setBlockIncrement(10);
        vSlider.setShowTickMarks(true);
        vSlider.setShowTickLabels(true);

        minField.setText("" + vSlider.getLowValue());
        maxField.setText("" + vSlider.getHighValue());

        minField.setEditable(false);
        minField.setPromptText("Min");

        maxField.setEditable(false);
        maxField.setPromptText("Max");

        minField.textProperty().bind(vSlider.lowValueProperty().asString("%.2f"));
        maxField.textProperty().bind(vSlider.highValueProperty().asString("%.2f"));

        VBox box = new VBox(10);
        box.setPadding(new Insets(0,0,0, 20));
//        box.setAlignment(Pos.CENTER);
        box.setFillWidth(false);
        box.getChildren().addAll(minField, vSlider, maxField);
        return box;
    }
    
}

Java3D Tutorials By Jonathan Miyamoto

Saturday, 23 November 2013

Loading, cropping, saving an image in Java

You'd typically
  1. Create a new BufferedImage with the desired width and height.
  2. Get hold of it's Graphics object
  3. Load the original .jpeg image
  4. Paint the desired part of that, onto the BufferedImage
  5. Write the buffered image out to file using ImageIO.
In code:
Image orig = ImageIO.read(new File("duke.jpg"));

int x = 10, y = 20, w = 40, h = 50;

BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
bi.getGraphics().drawImage(orig, 0, 0, w, h, x, y, x + w, y + h, null);

ImageIO.write(bi, "png", new File("duke_cropped.png"));
Given this .jpg...
...It generates this .png:

Java3d: How to programmatic-ally create a primitive and texture..?

  1. Use [TextureLoader][1]
    import com.sun.j3d.utils.image.TextureLoader;
  2. Create a texture
    Texture texImage = new TextureLoader(myCroppedbufferedImage).getTexture();
    //Texture texImage = new TextureLoader(myCroppedImage, this).getTexture();
  3. Set the texture, where app is an appearance object for your textured primitive
    app.setTexture (texImage);
On how to programmatically create a primitive and texture it see this example (adapted from source):
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;

import javax.media.j3d.Alpha;
import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Texture;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Point3d;

import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.image.TextureLoader; //that is the relevant part!
import com.sun.j3d.utils.universe.SimpleUniverse;

public class TextureImage extends Applet {

  private java.net.URL texImage = null;

  private SimpleUniverse u = null;

  public BranchGroup createSceneGraph() {
    // Create the root of the branch graph
    BranchGroup objRoot = new BranchGroup();

    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    objRoot.addChild(objTrans);

    // appearance object for textured cube
    Appearance app = new Appearance();
    Texture tex = new TextureLoader(texImage, this).getTexture();
    app.setTexture(tex);
    TextureAttributes texAttr = new TextureAttributes();
    texAttr.setTextureMode(TextureAttributes.MODULATE);
    app.setTextureAttributes(texAttr);

    // Create textured cube and add it to the scene graph.
    Box textureCube = new Box(0.4f, 0.4f, 0.4f,
        Box.GENERATE_TEXTURE_COORDS, app);
    objTrans.addChild(textureCube);

    Transform3D yAxis = new Transform3D();
    Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0,
        4000, 0, 0, 0, 0, 0);

    RotationInterpolator rotator = new RotationInterpolator(rotationAlpha,
        objTrans, yAxis, 0.0f, (float) Math.PI * 2.0f);
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);
    rotator.setSchedulingBounds(bounds);
    objTrans.addChild(rotator);

    // Have Java 3D perform optimizations on this scene graph.
    objRoot.compile();

    return objRoot;
  }

  public TextureImage() {
  }

  public TextureImage(java.net.URL url) {
    texImage = url;
  }

  public void init() {
    if (texImage == null) {
      // the path to the image for an applet
      try {
        texImage = new java.net.URL(getCodeBase().toString()
            + "/stone.jpg");
      } catch (java.net.MalformedURLException ex) {
        System.out.println(ex.getMessage());
        System.exit(1);
      }
    }
    setLayout(new BorderLayout());
    GraphicsConfiguration config = SimpleUniverse
        .getPreferredConfiguration();

    Canvas3D c = new Canvas3D(config);
    add("Center", c);

    // Create a simple scene and attach it to the virtual universe
    BranchGroup scene = createSceneGraph();
    u = new SimpleUniverse(c);

    // This will move the ViewPlatform back a bit so the
    // objects in the scene can be viewed.
    u.getViewingPlatform().setNominalViewingTransform();

    u.addBranchGraph(scene);
  }

  public void destroy() {
    u.cleanup();
  }

  //
  // The following allows TextureImage to be run as an application
  // as well as an applet
  //
  public static void main(String[] args) {
    java.net.URL url = null;
    if (args.length > 0) {
      try {
        url = new java.net.URL("file:" + args[0]);
      } catch (java.net.MalformedURLException ex) {
        System.out.println(ex.getMessage());
        System.exit(1);
      }
    } else {
      // the path to the image for an application
      try {
        url = new java.net.URL("file:stone.jpg");
      } catch (java.net.MalformedURLException ex) {
        System.out.println(ex.getMessage());
        System.exit(1);
      }
    }
    new MainFrame(new TextureImage(url), 256, 256);
  }

}