Sep 19

FontAwesomeFX 8.0.10 released

FontAwesomeFX 8.0.10 introduces Stacked Icons and FXML support

Binaries: Download
Sources: Bitbucket
Maven:

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>fontawesomefx</artifactId>
  <version>8.0.10</version>
</dependency>

Stacked Icons

FontAwesome is featuring Stacked Icons which is clearly also for JavaFX Applications this is a useful feature.

To support Stackend Icons FontAwesomeFX 8.0.10 comes with two new classes: AwesomeIconsStack and Icon.

AwesomeIconsStack
is basically a StackPane providing convent methods to add and remove Icons.

Icon
is a Node which is able to display a styleable FontAwesome icon. Icons can be styled in two ways:

by setting a style class:

Region stackedIcon2 = AwesomeIconsStack.create()
                .add(Icon.create()
                        .icon(AwesomeIcon.SQUARE)
                        .size("3em")
                        .styleClass("stack-base"))
                .add(Icon.create()
                        .icon(AwesomeIcon.STAR)
                        .size("2em")
                        .styleClass("stack-top")
                );

or by setting the style:

Region stackedIcon3 = AwesomeIconsStack.create()
                .add(Icon.create()
                        .icon(AwesomeIcon.SQUARE)
                        .style("-fx-font-size: 4em; -fx-text-fill: yellowgreen;"))
                .add(Icon.create()
                        .icon(AwesomeIcon.APPLE)
                        .style("-fx-font-size: 3em; -fx-text-fill: white;")
                );

fontawesomefx_8010_1

Thats the code to create Icons like above:

Region stackedIcon1 = AwesomeIconsStack.create().add(Icon.create());

Region stackedIcon2 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.SQUARE)
                .size("3em")
                .styleClass("stack-base"))
        .add(Icon.create()
                .icon(AwesomeIcon.STAR)
                .size("2em")
                .styleClass("stack-top")
        );

Region stackedIcon3 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.SQUARE)
                .style("-fx-font-size: 4em; -fx-text-fill: yellowgreen;"))
        .add(Icon.create()
                .icon(AwesomeIcon.APPLE)
                .style("-fx-font-size: 3em; -fx-text-fill: white;")
        );

Region stackedIcon4 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.SQUARE)
                .style("-fx-font-size: 4em; -fx-text-fill: yellowgreen;"))
        .add(Icon.create()
                .icon(AwesomeIcon.APPLE)
                .style("-fx-font-size: 3em; -fx-text-fill: black;")
        );

Region stackedIcon5 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.BUG)
                .style("-fx-font-size: 2em; -fx-text-fill: black;"))
        .add(Icon.create()
                .icon(AwesomeIcon.BAN)
                .style("-fx-font-size: 4em; -fx-text-fill: red; -fx-opacity: 0.5;")
        );

Region stackedIcon6 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.CIRCLE)
                .style("-fx-font-size: 8em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);"))
        .add(Icon.create()
                .icon(AwesomeIcon.TWITTER)
                .style("-fx-font-size: 4em; -fx-text-fill: white;")
        );

Region stackedIcon7 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.CIRCLE)
                .style("-fx-font-size: 12em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);"))
        .add(Icon.create()
                .icon(AwesomeIcon.BITBUCKET)
                .style("-fx-font-size: 6em; "
                        + "-fx-text-fill: linear-gradient(#ffffff, #d2d2d2); "
                        + "-fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 4 , 0.0 , 1 , 1 );")
        );

HBox stackIconBox = new HBox();
stackIconBox.setSpacing(5.0);
stackIconBox.getChildren().addAll(stackedIcon1, stackedIcon2, stackedIcon3, stackedIcon4, stackedIcon5, stackedIcon6, stackedIcon7);

Region iconStack1 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.CIRCLE)
                .style("-fx-font-size: 12em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);"))
        .add(Icon.create()
                .icon(AwesomeIcon.BITBUCKET)
                .style("-fx-font-size: 6em; "
                        + "-fx-text-fill: linear-gradient(#ffffff 0%, #d2d2d2); "
                        + "-fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 4 , 0.0 , 1 , 1 );"))
        .add(new Icon(AwesomeIcon.BAN, null, "-fx-font-size: 12em; -fx-text-fill: red; -fx-opacity: 0.5;", null));

HBox stackIconBox2 = new HBox();
stackIconBox2.setSpacing(5.0);
stackIconBox2.getChildren().addAll(iconStack1);

Have a look at “iconStack1” in the code above:
the number of stacked Icons is not limited to only two.

Support for FXML

In respect to the issue opened by Manuel Maukys (@manuel_mauky) I played with the new @NamedArg annotation. It’s also possible to use the Icon in FXML (even stacked):

fontawesomefx_8010_2

<VBox alignment="CENTER" spacing="10.0">
  <children>
    <Icon awesomeIcon="STAR" size="4em" style="-fx-font-size: 6em; -fx-text-fill: linear-gradient(#ffffff, #d2d2d2); -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.5) , 8 , 0.0 , 0 , 0 );" styleClass="" />
    <StackPane>
      <children>
        <Icon awesomeIcon="CIRCLE" style="-fx-font-size: 24em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);" styleClass="" size=""/>
        <Icon awesomeIcon="BITBUCKET" style="-fx-font-size: 12em; -fx-text-fill: linear-gradient(#ffffff, #d2d2d2); -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 4 , 0.0 , 1 , 1 );" styleClass="" size=""/>
      </children>
    </StackPane>
  </children>
</VBox>

I am not a Ninja master yet, but somehow I imported a custom component to get it also supported in SceneBuilder 2.0:

fontawesomefx_8010_3

Sep 13

MQTT.fx 0.0.9 released

A new version of MQTT.fx is available (probably the last release before JavaOne)

Download latest binaries in all flavours: mqttfx.org.
Bugs and feature requests can be reported via the issue tracker.

This release comes with some new features:

Message Font Size

(in respect to Gerrit Grunwald @hansolo_)
To improve ledibility of large message bodies now the font size of messages can be set to a fixed value (0.6em – 1.5em) or a dynamic size depending on message size (1.0em – 0.6em).
You can open the new Settings Dialog via Menu Extras / Settings:

settings

Dynamic message font size:

fonts

Clear History

(also in respect to Gerrit Grunwald)
The Connection Profile Dialog (Section “General”) and the Publish/Subscribe panels now the history of last topics can be cleared.

clear_publish

clear_sub

clear_profile

“Never again drive me crazy with silly questions”

(in respect to David Janes @dpjanes)
The Exit Dialog now offers an option “Do not ask me again!” to get not asked again when closing the app.

exit

Bugfixes

Also I again have fixed some bugs (typos, usability, layout + style issues).

Aug 28

FontAwesomeFX 8.0.9 with 40 new icons

I have updated FontAwesomeFX and released version 8.0.9.
A few days ago the FontAwesome 4.2.0 was released with 40 additional icons (FontAwesome now contains 479 icons).

Also I added a simple browser to preview all contained icons.
You can start it via: de.jensd.fx.fontawesome.test.IconOverview

iconsbrowser_8.0.9

Download Binaries
Sources @ Bitbucket

Maven Artifact:

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>fontawesomefx</artifactId>
  <version>8.0.9</version>
</dependency>
Aug 27

MQTT.fx 0.0.7 released

A new version of MQTT.fx is available featuring:

  • Scripting support
  • Setup/Re-configuration tool
  • Reviewed Connection Profile Editor
  • Reviewed UI and style

007_start

Download latest binaries HERE.

Scripting support

I have added support for scripts executed by the Nashorn Engine.
Thought it might be helpful for testing a MQTT message flow of a certain setup, e.g. simulate sensors sending temperature value messages. All controlled via the UI.

007_executescript

Adding new scripts

New scripts can be easily made available for execution via the UI. The app is scanning a folder named “scripts” located at the MQTT.fx working directory:
OS X: [USER_HOME]/Library/Application Support/MQTT-FX/scripts
Windows: [USER_HOME]\AppData\Local\MQTT-FX\scripts
Linux: [USER_HOME]/MQTT-FX/scripts
If this folder does not exists it is created by MQTT.fx containing a demo script.

007_workingdir

Naming convention:

[INDEX]__[NAME].js
01__Switch_Fountain_Test.js

[INDEX] - controls the order of the entry in the drop down menu
__ - delimiter between index and name
[NAME] - the name of the menu entry (words are delimit by '_')

Hint:
If you name a script “separator”, e.g. 02__separator.js a separator is added to the menu at the “index” position.

007_scripting

Example script:

var Thread = Java.type("java.lang.Thread");
var System = Java.type('java.lang.System');

function execute(action) {
    out("Test Script: " + action.getName());
    for (var i = 0; i < 10; i++) {
        switchON();
        Thread.sleep(500);
        switchOFF();
        Thread.sleep(500);
    }
    action.setExitCode(0);
    action.setResultText("done.");
    out("Test Script: Done");
    return action;
}

function switchON() {
    out("fountain ON");
    mqttManager.publish("home/garden/fountain", "ON");
}

function switchOFF() {
    out("fountain OFF");
    mqttManager.publish("home/garden/fountain", "OFF");
}

function out(message){
    System.out.println(message);
}

The entry point of execution is the function execute(action).
During script execution System.out is hooked by MQTT.fx to print output to the console. Thus with System.out.println() messaged can be printed to the UI-console.

The interface to the broker-connection is the bound mqttManager. Messages can be published via the established broker connection.

Setup/Re-configuration Tool

If no configuration can be found, the app assumes its first start. The user is promoted with a setup dialog to create e new configuration.

Also if a configuration has been found but it seems to be corrupt or the format doesn't fit a newer version also a new configuration can be created or the exiting config can be opened in an external editor (assumed an editor has been assigned to open *.xml-files.

007_setup

Reviewed Connection Profile Editor
Now the Connection Profile Editor is recognising wether the chosen profile has been changed (buttons are enabled/disabled accordingly). Changes can be reverted as not already applied.

007_newprofile

Reviewed UI and Style

Greetings from Yosemity! ;-)

Aug 20

Poor mans input constraints

For me the best way to prevent the user from annoying errors and error messages is simply in the first line to try not to allow the user e.g. to enter invalid data or to execute actions while data in incomplete (disabled buttons/menu entries).
If only numbers allowed in an input field I think its a better approach to restict user input to numbers only instead of validating and popping up with error messages after applying or “OK”.

Swing has a JFormattedTextField which allows setting a format pattern to verify the input data. Sadly the current JavaFX API doesn’t contain such kind of control but thers is a way by adding an EventHandler for KEY_TYPED-Events and to consume the event at certain conditions. Consuming the event will end up the event bubbling phase. (see Handling JavaFX Events for more informations).
Clearly this can be implemented in a new control or by creating a subclass of TextField. But there is also a simple approach by attaching the wanted constraints to existing instances of standard controls. As you my know the editor controls of Cells and ComboBoxes are also TextFields. Thus such a handler can also be attached e.g. to a ComboBox.

Limitation/Disclaimer:
Don’t sue me for this very simple approach ;-)!
As this is just a limitaion of the INPUT while TYPING it can be bypassed e.g. by copy-paste.
Clearly in the long run a JFormattedTextField like control supporting pattern matching, validation and user feedback is needed!

Runnable demo and code can be found here.

InputConstraintsDemo

        InputConstraints.noBlanks(noBlanksField);
        InputConstraints.noLeadingBlanks(noLeadingBlanksField);
        InputConstraints.numbersOnly(numbersOnlyField);
        InputConstraints.lettersOnly(lettersOnlyField);

        InputConstraints.noLeadingBlanks(valueComboBox.getEditor());
        InputConstraints.noLeadingBlanks(noLeadingBlanksComboBox.getEditor());
        InputConstraints.noBlanks(noBlanksComboBox.getEditor());
        InputConstraints.numbersOnly(numbersOnlyComboBox.getEditor());
        InputConstraints.lettersOnly(lettersOnlyComboBox.getEditor());
public class InputContraints{

    private InputContraints() {
        // not needed here
    }

    public static void noLeadingAndTrailingBlanks(final TextField textField) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNoLeadingBlanksInputHandler());
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static void noLeadingBlanks(final TextField textField) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNoLeadingBlanksInputHandler());
    }

    public static void noBlanks(final TextField textField) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNoBlanksInputHandler());
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static void numbersOnly(final TextField textField) {
        numbersOnly(textField, Integer.MAX_VALUE);
    }

    public static void numbersOnly(final TextField textField, final Integer maxLenth) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createNumbersOnlyInputHandler(maxLenth));
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static void lettersOnly(final TextField textField) {
        lettersOnly(textField, Integer.MAX_VALUE);
    }

    public static void lettersOnly(final TextField textField, final Integer maxLenth) {
        textField.addEventFilter(KeyEvent.KEY_TYPED, createLettersOnlyInputHandler(maxLenth));
        textField.focusedProperty().addListener((Observable observable) -> {
            textField.setText(textField.getText().trim());
        });
    }

    public static EventHandler<KeyEvent> createNoLeadingBlanksInputHandler() {
        return (KeyEvent event) -> {
            if (event.getSource() instanceof TextField) {
                TextField textField = (TextField) event.getSource();
                if (" ".equals(event.getCharacter()) && textField.getCaretPosition() == 0) {
                    event.consume();
                }
            }
        };
    }

    public static EventHandler<KeyEvent> createNumbersOnlyInputHandler(final Integer maxLength) {
        return createPatternInputHandler(maxLength, "[0-9.]");
    }

    public static EventHandler<KeyEvent> createLettersOnlyInputHandler(final Integer maxLength) {
        return createPatternInputHandler(maxLength, "[A-Za-z]");
    }

    public static EventHandler<KeyEvent> createNoBlanksInputHandler() {
        return (KeyEvent event) -> {
            if (event.getSource() instanceof TextField) {
                if (" ".equals(event.getCharacter())) {
                    event.consume();
                }
            }
        };
    }

    public static EventHandler<KeyEvent> createPatternInputHandler(final Integer maxLength, String pattern) {
        return (KeyEvent event) -> {
            if (event.getSource() instanceof TextField) {
                TextField textField = (TextField) event.getSource();
                if (textField.getText().length() >= maxLength || !event.getCharacter().matches(pattern)) {
                    event.consume();
                }
            }
        };
    }
}
Aug 12

MQTT.fx 0.0.6 released

I have just released a new version of MQTT.fx (0.0.6).

Download latest binaries HERE.

Introducing Connection Profiles
Instead of choosing a certain broker address and a broker port using the same connection options, I now have implemented Connection Profiles.

A named Connection Profile contains all information for a certain broker connection (address, port, Auth/SSL/TLS settings, Last Will/Testament, etc.). It’s much easier now to connect to different brokers with changed setups and connection options:

mqtt-connprofiles

Now a connection can be established by just choosing the name of a previous defined profile:

mqtt-connprofiles2

Support for SSL/TLS

Also I have started to implement support for SSL/TLS secured connections.
Unsecure:
MQTT.fx - v0.0.6 - connected unsecure
Secured:
MQTT.fx - v0.0.6 - connected secure

Support for Certificate Files
mqtt-certificates

Support for Keystores
Edit Connection Profiles keytores

Hint:
Again the configuration-format has changed, v0.0.6 will conflict with former versions and MQTT.fx won’t start.
In this case just delete (or rename) the mqttfx-config.xml.
Windows: [USER_HOME]\AppData\Local\MQTT-FX\mqttfx-config.xml
OS X: [USER_HOME]/Library/Application Support/MQTT-FX/mqttfx-config.xml

The new configration is automatically created on next app start.