ShichimiFX 1.0.5 released

I have released ShichimiFX 1.0.5.

Get more info and the code from my ShichimiFX Bitbucket Repo

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>shichimifx</artifactId>
  <version>1.0.5</version>
</dependency>

UI for Refuel

This release contains a Frontend for Refuel an application update API written by Dino Tsoumakis which is used with MQTT.fx 0.0.13.

updates-available

updates-load

ShichimiFX 1.0.4 released

I have released ShichimiFX 1.0.4.

Get the code from my ShichimiFX Bitbucket Repo

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>shichimifx</artifactId>
  <version>1.0.4</version>
</dependency>

This release contains 2 new utility classes I needed for MQTT.fx‘s script editing function Desktop and Shell.

By now I used to open a script for editing with the system default editor via java.awt.Desktop. So far this works well on my Mac but there were issues on Windows and Linux. To get rid of these issues I create my own Desktop class to open the script directly with the system shell:

Desktop

public class Desktop {

    public static boolean edit(String fileToEdit) {
        return openWithSystem(fileToEdit);
    }

    private static boolean openWithSystem(String fileToOpen) {
        if (OS.isLinux()) {
            if (Shell.canOpen("gnome-open", fileToOpen)) {
                return true;
            } else if (Shell.canOpen("kde-open", fileToOpen)) {
                return true;
            } else if (Shell.canOpen("xdg-open", fileToOpen)) {
                return true;
            }
        } else if (OS.isWindows()) {
            if (Shell.canOpen("explorer", fileToOpen)) {
                return true;
            }
        } else if (OS.isMac()) {
            if (Shell.canOpen("open", fileToOpen)) {
                return true;
            }
        }
        return false;
    }
}

Shell

public class Shell {

    public static boolean canOpen(String... command) {
        try {
            ProcessBuilder builder = new ProcessBuilder(command);
            Process process = builder.directory(new File(System.getProperty("user.home"))).start();
            if (process == null) {
                return false;
            }
            try {
                process.exitValue();
                // if this does not throw an IllegalThreadStateException the process is not running/has exited immediately
                return false;
            } catch (IllegalThreadStateException e) {
                // the process seems to be in a running state
                return true;
            }
        } catch (IOException ex) {
            return false;
        }
    }

    public static List<String> prepareOpenFileLine(String command, String fileToOpen) {
        List<String> commandList = new ArrayList<>();
        for (String s : command.split(" ")) {
            commandList.add(s.trim());
        }
        commandList.add(fileToOpen);
        return commandList;
    }

    public static Optional<Process> execute(List<String> command) throws IOException {
        return execute(new File(System.getProperty("user.home")), command);
    }

    public static Optional<Process> execute(File directory, String... command) throws IOException {
        return execute(directory, Arrays.asList(command));
    }

    public static Optional<Process> execute(String... command) throws IOException {
        return Shell.execute(new File(System.getProperty("user.home")), command);
    }

    public static Optional<Process> execute(File directory, List<String> command) throws IOException {
        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.directory(directory).start();
        return Optional.of(process);
    }
}

ShichimiFX 1.0.3 released

I have released ShichimiFX 1.0.3.

Get the code from my ShichimiFX Bitbucket Repo

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>shichimifx</artifactId>
  <version>1.0.3</version>
</dependency>

This release mostly focuses on the ConsoleStream. I have fixed some performance issues. Also optionally now an alternate stream can be given, e.g. to pass through System.out.
This is used for displaying the logging stream in my lastest release of MQTT.fx.

mqttfx-log-tab

public class ConsoleDude {

    public final static PrintStream STD_OUT = System.out;
    public final static PrintStream STD_ERR = System.err;
    private static BooleanProperty stdStreamsHooked = new SimpleBooleanProperty();

    public static void hookStdStreams(TextInputControl outputArea) {
        PrintStream psOut = new PrintStream(new ConsoleStream(outputArea, System.out), true);
        System.setOut(psOut);
        PrintStream psErr = new PrintStream(new ConsoleStream(outputArea, System.err), true);
        System.setOut(psErr);
        stdStreamsHookedProperty().set(true);
    }

    public static void restoreStdStreams() {
        System.setOut(STD_OUT);
        System.setErr(STD_ERR);
        stdStreamsHookedProperty().set(false);
    }

    public static BooleanProperty stdStreamsHookedProperty() {
        if (stdStreamsHooked == null) {
            stdStreamsHooked = new SimpleBooleanProperty();
        }
        return stdStreamsHooked;
    }

    public boolean isHooked() {
        return stdStreamsHookedProperty().get();
    }

    public static ConsoleStreamHandler createConsoleStreamHandler(TextInputControl outputArea) {
        return createConsoleStreamHandler(outputArea, new SimpleFormatter());
    }

    public static ConsoleStreamHandler createConsoleStreamHandler(TextInputControl outputArea, Formatter formatter) {
        return new ConsoleStreamHandler(new ConsoleStream(outputArea), formatter);
    }

}
public class ConsoleStream extends ByteArrayOutputStream {

    private final TextInputControl textOutputComponent;
    private final StringBuilder buffer;
    private final String EOL = System.getProperty("line.separator");
    private final PrintStream printStream;

    public ConsoleStream(TextInputControl ta) {
        this(ta, null);
    }

    public ConsoleStream(TextInputControl ta, PrintStream printStream) {
        this.textOutputComponent = ta;
        this.printStream = printStream;
        buffer = new StringBuilder(80);
    }

    @Override
    public void flush() throws IOException {
        String text = toString();
        if (text.length() == 0) {
            return;
        }
        append(text);
        reset();
    }

    private void append(String text) {
        if (textOutputComponent.getLength() == 0) {
            buffer.setLength(0);
        }
        if (EOL.equals(text)) {
            buffer.append(text);
        } else {
            buffer.append(text);
            clearBuffer();
        }
    }

    private void clearBuffer() {
        String line = buffer.toString();
        Platform.runLater(() -> {
            textOutputComponent.appendText(line);
        });
        if (printStream != null) {
            printStream.print(line);
        }
        buffer.setLength(0);
    }

}

ShichimiFX 1.0.1 released

I have added extracted some more classes from my application projects (e.g. from MQTT.fx) and added them as new features to ShichimiFX (and released 1.0.1).

Get the code from my ShichimiFX Bitbucket Repo

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>shichimifx</artifactId>
  <version>1.0.1</version>
</dependency>

AnimationDude
Provides support for Fade-in/Fade-out-Transition of controls:
AnimationDude.addInOutFadeTransition(Duration duration, Node node)
AnimationDude.addInOutFadeTransition(Duration duration, Node nodeToFade, Node trigger)

Make a ListCell (this) a trigger to to fade in a button bar on mouse hover:
AnimationDude.addInOutFadeTransition(Duration.millis(200), buttonsBox, this);

public class AnimationDude {
    
    public final static void  addInOutFadeTransition(Duration duration, Node node) {
        addInOutFadeTransition(duration, node, node);
    }

    public final static void  addInOutFadeTransition(Duration duration, Node nodeToFade, Node trigger) {
        nodeToFade.setOpacity(0.0);
        final FadeTransition fadeOutTransition = new FadeTransition(duration, nodeToFade);
        fadeOutTransition.setFromValue(1.0);
        fadeOutTransition.setToValue(0.0);
        final FadeTransition fadeInTransition = new FadeTransition(duration, nodeToFade);
        fadeInTransition.setFromValue(0.0);
        fadeInTransition.setToValue(1.0);
        trigger.setOnMouseEntered(e->{
                    fadeOutTransition.stop();
                    fadeInTransition.play();
        });
        trigger.setOnMouseExited(e->{
                    fadeInTransition.stop();
                    fadeOutTransition.play();
        });
    }
}

ConsoleDude
Provides support for switching System.out/Sytem.err (ConsoleStream)or bypassing a logging output (ConsoleStreamHandler) a JavaFX text components.

Usage

Use “stdOutMessageArea” for System.out and System.err output:
hook StdOut + StdErr: ConsoleDude.hookStdOutTo(stdOutMessageArea);
restore StdOut + StdErr: ConsoleDude.restoreStdOut();

Also you can observe the status, e. g.:
hookButton.disableProperty().bind(ConsoleDude.stdOutHookedProperty());
releaseButton.disableProperty().bind(ConsoleDude.stdOutHookedProperty().not());

Use “logMessageArea” for logging output:
LOGGER.addHandler(ConsoleDude.createConsoleStreamHandler(logMessageArea));

public class ConsoleStream extends OutputStream {

    private final TextInputControl output;

    public ConsoleStream(TextInputControl ta) {
        this.output = ta;
    }

    @Override
    public void write(int i) throws IOException {
        Platform.runLater(() -> {
            output.appendText(String.valueOf((char) i));
        });
    }
}
public class ConsoleStreamHandler extends StreamHandler{

    public ConsoleStreamHandler(OutputStream out, Formatter formatter) {
        super(out, formatter);
    }

    @Override
    public synchronized void publish(LogRecord record) {
        super.publish(record);
        flush();
    }
}
public class ConsoleDude {

    public final static PrintStream STD_OUT = System.out;
    public final static PrintStream STD_ERR = System.err;
    private static BooleanProperty stdOutHooked = new SimpleBooleanProperty();

    public static void hookStdOutTo(TextInputControl outputArea) {
        PrintStream ps = new PrintStream(new ConsoleStream(outputArea), true);
        System.setOut(ps);
        System.setErr(ps);
        stdOutHookedProperty().set(true);
    }

    public static void restoreStdOut() {
        System.setOut(STD_OUT);
        System.setErr(STD_ERR);
        stdOutHookedProperty().set(false);
    }

    public static BooleanProperty stdOutHookedProperty() {
        if (stdOutHooked == null) {
            stdOutHooked = new SimpleBooleanProperty();
        }
        return stdOutHooked;
    }

    public boolean isHooked() {
        return stdOutHookedProperty().get();
    }

    public static ConsoleStreamHandler createConsoleStreamHandler(TextInputControl outputArea) {
        return createConsoleStreamHandler(outputArea,new SimpleFormatter());
    }

    public static ConsoleStreamHandler createConsoleStreamHandler(TextInputControl outputArea, Formatter formatter) {
        return new ConsoleStreamHandler(new ConsoleStream(outputArea), formatter);
    }

}

SplitPane.Divider Transition

Wrote a small utility class to control animated sliding of a SplitPane.Divider.

You can find the complete code in my Bitbucket repo here.

I intend to make it as simple as possible to attach a slide-animation to a standard SplitPane:

splitPane          The SplitPane to be animated.
dividerIndex     Index of the divider to be controlled.
direction           Direction to slide out the layout container
(horizontal SplitPane: LEFT/RIGHT, vertical SplitPane: UP/DOWN)
cycleDuration   Duration/Speed of the slide animation.

SplitPaneDividerSlider( SplitPane splitPane,
                                int dividerIndex,
                                Direction direction,
                                Duration cycleDuration )

SplitPaneTransitionDemo1

E.g. create a divider controller for divider at index 0:

SplitPaneDividerSlider leftSplitPaneDividerSlider = new SplitPaneDividerSlider(centerSplitPane, 0, SplitPaneDividerSlider.Direction.LEFT);

This guy can be triggered by a ToggleButton:

leftToggleButton.selectedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
leftSplitPaneDividerSlider.setAimContentVisible(t1);
});

Set a font-icon on the ToogleButton:

AwesomeDude.setIcon(leftToggleButton, AwesomeIcon.TOGGLE_LEFT, "2em");

leftToggleButton.selectedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
      if (t1) {
          AwesomeDude.setIcon(leftToggleButton, AwesomeIcon.TOGGLE_LEFT, "2em"
      } else {
           AwesomeDude.setIcon(leftToggleButton, AwesomeIcon.TOGGLE_RIGHT, "2.5em")          
      }
});

SplitPanes in JavaFX do respect the min/max sizes of layout containers.

Movement of the divider is stopped when reaching these values.
To overrule this constraint when animation is started to slide out the content, min size is set to 0.0 and the original size will be restored when sliding in the content is finished. The right pane is figured out based on the divider index and the direction to side out:

        switch (direction) {
            case LEFT:
            case UP:
                content = (Region) splitPane.getItems().get(dividerIndex);
                break;
            case RIGHT:
            case DOWN:
                content = (Region) splitPane.getItems().get(dividerIndex + 1);
                break;
        }

Custom Component: TouchPad Control

Playing around with the Canvas I have implemented a TouchPad like Control.

xValueProperty and yValueProperty are provided in a range from 0.0…1.0 respectively -1.0..1.0.

Thus this control can be used like an “Input-Device”, e.g. a Joystick (use two of them to operate a Quadrocopter ;-)) or a PitchControl (like the FX control of “DM1 – The Drum Machine” from Fingerlab):

Custom Component: SelectableTitledPane

I’m working on a form to provide search criteria to find pictures in picmodo.
My goal is to provide a very easy to use interface.

Each search criteria is represented by a SelectableTitledPane which contains the parameter settings.
Basically I have set a CheckBox as graphic component of a TitlePane and modified the CSS of the Default-Skin com.sun.javafx.scene.control.skin.TitledPaneSkin.

picmodo search panel

The Checkbox‘s selectedProperty is bound to the expandedProperty of TitledPane to toggle the expand state and to get an indicator to enable / disable the criteria:

public class SelectableTitledPane extends TitledPane {

  private CheckBox checkBox;

  public SelectableTitledPane(String title, Node content) {
    super(title, content);
    checkBox = new CheckBox(title);
    checkBox.selectedProperty().
            bindBidirectional(this.expandedProperty());
    setExpanded(false);
    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
    setGraphic(checkBox);
    setSkin(new TitledPaneSkin(this));
    lookup(".arrow").
            setVisible(false);
    lookup(".title").
            setStyle("-fx-padding: 0 0 4 -10;"
            + "-fx-background-color: null;");
    lookup(".content").
            setStyle("-fx-background-color: null; -fx-padding:  0.2em 0.2em 0.2em 1.316667em;");
  }

  public BooleanProperty getSelectedProperty() {
    return checkBox.selectedProperty();
  }

  public boolean isSelected() {
    return checkBox.isSelected();
  }

  public void setSelected(boolean selected) {
    checkBox.setSelected(selected);
  }
}

Basically the SelectableTitledPane can then be used as a TitledPane, e.g.:

ComboBox ownerBox = ComboBoxBuilder.<AppUser>create().
            prefWidth(150).
            promptText(resourceBundle
            .getString("search.prompt.owner")).
            build();

SelectableTitledPaneownerParams = new SelectableTitledPane(resourceBundle
            .getString("search.checkbox.owner"), ownerBox);

Custom Component: Starter with hover effect

I was looking for a starter component for to use on the dock like main entry page of our application. It should have a hover effect on mouse enter/ mouse exit and a start effect on mouse click.

As JavaFX has already very useful Transition implementations I gave the ScaleTransition a try. I wanted a smooth expand and shrink effect on mouse enter and exit. Unfortunately transitions can on be played backwards. Reverse play is only supported when using the auto reverse option combined with an appropriate cycle count.

But I saw that changing the properties of an existing ScaleTransition instance and playFromStart() is a working approach:

        setOnMouseEntered(new EventHandler()
        {
            @Override
            public void handle(MouseEvent t)
            {
                scaleTransition.setFromX(getScaleX());
                scaleTransition.setFromY(getScaleY());
                scaleTransition.setToX(expandToMaxProperty.get());
                scaleTransition.setToY(expandToMaxProperty.get());
                scaleTransition.playFromStart();
            }
        });

        setOnMouseExited(new EventHandler()
        {
            @Override
            public void handle(MouseEvent t)
            {
                scaleTransition.setFromX(getScaleX());
                scaleTransition.setFromY(getScaleY());
                scaleTransition.setToX(1);
                scaleTransition.setToY(1);
                scaleTransition.playFromStart();
            }
        });

The start effect played on mouse click is a combination of both sequential and parallel played transitions: scale and fade out are played in parallel and in the end the starter goes back to an initial position:

        fadeTransition = new FadeTransition(Duration.millis(200), this);
        fadeTransition.setCycleCount(1);
        fadeTransition
                .setInterpolator(Interpolator.EASE_BOTH);
      
        startTransition = new ParallelTransition();
        startTransition.setCycleCount(2);
        startTransition.setAutoReverse(true);
        startTransition.getChildren()
                .addAll(scaleTransition,
                fadeTransition);

        initTransition = new ScaleTransition(Duration.millis(200), this);
        initTransition.setToX(1);
        initTransition.setToY(1);
        initTransition.setCycleCount(1);
        initTransition
                .setInterpolator(Interpolator.EASE_BOTH);

        startsSequentialTransition = new SequentialTransition();
        startsSequentialTransition.getChildren()
                .addAll(
                startTransition,
                initTransition);

Whole Starter code

public class Starter extends StackPane
{

    private ParallelTransition startTransition;
    private ScaleTransition scaleTransition;
    private ScaleTransition initTransition;
    private FadeTransition fadeTransition;
    private SequentialTransition startsSequentialTransition;
    private DoubleProperty expandToMaxProperty;
    private ImageView baseImage;

    public Starter(String base, double fitWidth, double fitHeight)
    {
        baseImage = new ImageView(base);
        init(fitWidth, fitHeight);
    }

    public Starter(Image base, double fitWidth, double fitHeight)
    {
        baseImage = new ImageView(base);
        init(fitWidth, fitHeight);
    }

    private void init(double fitWidth, double fitHeight)
    {
        expandToMaxProperty = new SimpleDoubleProperty(1.2);

        baseImage.setFitWidth(fitWidth);
        baseImage.setFitHeight(fitHeight);

        Color selectedColor = Color.rgb(0, 0, 0, 0.5);
        DropShadow dropShadow = DropShadowBuilder.create()
                .color(selectedColor)
                .build();
        setEffect(dropShadow);

        dropShadow.radiusProperty()
                .bind(scaleXProperty()
                .multiply(8));
        dropShadow.offsetXProperty()
                .bind(scaleXProperty()
                .multiply(8));
        dropShadow.offsetYProperty()
                .bind(scaleYProperty()
                .multiply(8));

        scaleTransition =
                new ScaleTransition(Duration.millis(200), this);
        scaleTransition.setCycleCount(1);
        scaleTransition
                .setInterpolator(Interpolator.EASE_BOTH);



        fadeTransition = new FadeTransition(Duration.millis(200), this);
        fadeTransition.setCycleCount(1);
        fadeTransition
                .setInterpolator(Interpolator.EASE_BOTH);

        startTransition = new ParallelTransition();
        startTransition.setCycleCount(2);
        startTransition.setAutoReverse(true);
        startTransition.getChildren()
                .addAll(scaleTransition,
                fadeTransition);

        initTransition =
                new ScaleTransition(Duration.millis(200), this);
        initTransition.setToX(1);
        initTransition.setToY(1);
        initTransition.setCycleCount(1);
        initTransition
                .setInterpolator(Interpolator.EASE_BOTH);

        startsSequentialTransition = new SequentialTransition();
        startsSequentialTransition.getChildren()
                .addAll(
                startTransition,
                initTransition);

        getChildren()
                .addAll(baseImage);

        setOnMouseEntered(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent t)
            {
                scaleTransition.setFromX(getScaleX());
                scaleTransition.setFromY(getScaleY());
                scaleTransition.setToX(expandToMaxProperty.get());
                scaleTransition.setToY(expandToMaxProperty.get());
                scaleTransition.playFromStart();
            }
        });

        setOnMouseExited(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent t)
            {
                scaleTransition.setFromX(getScaleX());
                scaleTransition.setFromY(getScaleY());
                scaleTransition.setToX(1);
                scaleTransition.setToY(1);
                scaleTransition.playFromStart();
            }
        });

        setOnMouseClicked(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent t)
            {
                scaleTransition.setFromX(getScaleX());
                scaleTransition.setFromY(getScaleY());
                scaleTransition.setToX(2);
                scaleTransition.setToY(2);
                fadeTransition.setFromValue(1.0f);
                fadeTransition.setToValue(0.5f);
                startsSequentialTransition.playFromStart();
            }
        });
    }

    public DoubleProperty expandToMaxProperty()
    {
        return expandToMaxProperty;
    }
}

[ U P D A T E 21-10-2014]

Made some modifications to get the Starter running on Java 8:

public class Starter extends StackPane
{

    private ParallelTransition startTransition;
    private ScaleTransition scaleTransition;
    private ScaleTransition startScaleTransition;
    private ScaleTransition initTransition;
    private FadeTransition fadeTransition;
    private SequentialTransition startsSequentialTransition;
    private DoubleProperty expandToMaxProperty;
    private final ImageView baseImage;

    public Starter(String base, double fitWidth, double fitHeight)
    {
        baseImage = new ImageView(base);
        init(fitWidth, fitHeight);
    }

    public Starter(Image base, double fitWidth, double fitHeight)
    {
        baseImage = new ImageView(base);
        init(fitWidth, fitHeight);
    }

    private void init(double fitWidth, double fitHeight)
    {
        expandToMaxProperty = new SimpleDoubleProperty(1.2);

        baseImage.setFitWidth(fitWidth);
        baseImage.setFitHeight(fitHeight);

        Color selectedColor = Color.rgb(0, 0, 0, 0.5);
        DropShadow dropShadow = DropShadowBuilder.create()
                .color(selectedColor)
                .build();
        setEffect(dropShadow);

        dropShadow.radiusProperty()
                .bind(scaleXProperty()
                .multiply(8));
        dropShadow.offsetXProperty()
                .bind(scaleXProperty()
                .multiply(8));
        dropShadow.offsetYProperty()
                .bind(scaleYProperty()
                .multiply(8));

        scaleTransition =
                new ScaleTransition(Duration.millis(200), this);
        scaleTransition.setCycleCount(1);
        scaleTransition
                .setInterpolator(Interpolator.EASE_BOTH);

        startScaleTransition =
                new ScaleTransition(Duration.millis(200), this);
        startScaleTransition.setCycleCount(1);
        startScaleTransition
                .setInterpolator(Interpolator.EASE_BOTH);

        fadeTransition = new FadeTransition(Duration.millis(200), this);
        fadeTransition.setCycleCount(1);
        fadeTransition
                .setInterpolator(Interpolator.EASE_BOTH);

        startTransition = new ParallelTransition();
        startTransition.setCycleCount(2);
        startTransition.setAutoReverse(true);
        startTransition.getChildren()
                .addAll(startScaleTransition, fadeTransition);

        initTransition =
                new ScaleTransition(Duration.millis(200), this);
        initTransition.setToX(1);
        initTransition.setToY(1);
        initTransition.setCycleCount(1);
        initTransition
                .setInterpolator(Interpolator.EASE_BOTH);

        startsSequentialTransition = new SequentialTransition();
        startsSequentialTransition.getChildren()
                .addAll(
                startTransition,
                initTransition);

        getChildren()
                .addAll(baseImage);

        setOnMouseEntered((MouseEvent t) -> {
            scaleTransition.setFromX(getScaleX());
            scaleTransition.setFromY(getScaleY());
            scaleTransition.setToX(expandToMaxProperty.get());
            scaleTransition.setToY(expandToMaxProperty.get());
            scaleTransition.playFromStart();
        });

        setOnMouseExited((MouseEvent t) -> {
            scaleTransition.setFromX(getScaleX());
            scaleTransition.setFromY(getScaleY());
            scaleTransition.setToX(1);
            scaleTransition.setToY(1);
            scaleTransition.playFromStart();
        });

        setOnMouseClicked((MouseEvent t) -> {
            startScaleTransition.setFromX(getScaleX());
            startScaleTransition.setFromY(getScaleY());
            startScaleTransition.setToX(2);
            startScaleTransition.setToY(2);
            fadeTransition.setFromValue(1.0f);
            fadeTransition.setToValue(0.5f);
            startsSequentialTransition.playFromStart();
        });
    }

    public DoubleProperty expandToMaxProperty()
    {
        return expandToMaxProperty;
    }
}

Find complete code repo at BitBucket.