Saturday, September 22, 2012

A multi-touching experience with a WeTab

Hi there!

This time my post won't be strictly code related. Instead I'll share with you my recent experience with a WeTab, a tablet computer I have for a couple of years, trying to test a few JavaFX multitouch enabled samples from the JFXtras project, from Carl Dea (@carldea) and Gerrit Grunwald (@hansolo_).

First of all, let me introduce you to the WeTab tablet, for those of you who haven't heard of it yet. Instead of boring you with technical data, here you can find the specs, and here you have a very good review of the WeTab. But in short, it includes a (big) 11.6" TN-panel touch screen (1366×768 resolution) powered with Intel GMA 3150 graphics and PCIe x1 Broadcom CrystalHD BCM70015, a 1.66 GHz Intel Atom N450 processor with fan, 1 GB RAM memory, 16 or 32 GB  flash memory, WiFi and optional 3G, 2 USB ports, and a total weight of the device of 1.002 kg.


I knew of it back in 2010, thanks to my partner Ricardo Vela (@rikivela), when we were developing client and server Java based projects, and we felt the necessity to go wireless with our client applications. So we searched for a tablet where we could run java and he found this linux based tablet with MeeGo technology. It was only sold in Germany by then, so we have to buy it through Amazon.de store.
 
So at the beginning of 2011 I had in my hands this heavy tablet. It took me a while to refresh my rusty linux knowledge, but I was able to download and install the last JRE linux version, and test that my application ran on it really nice and smooth. I was so excited I took a (very bad) picture of it:



Even it had its plastic cover on it! I realized I have to fix the screen size of my app, but apart from that, you know what they say: write once, run everywhere.

Then we had the chance to make an iOS version for iPhone and iPad tablet of the same project, and we found the pros and cons of it: The WeTab screen had nothing to do with the Retina Display of iOS devices... but we have to re-write all the client app to Objetive-C. Anyway, this is a story for another day.

For several reasons I had my WeTab parked somewhere, until recently, when Carl Dea tweeted about finding a linux tablet to run JavaFX. I offered myself, and then... the story begins...

Java, JavaFX and MeeGo

If you want to test Java and JavaFX based applications you have to install the last JRE in the tablet. There're plenty of blogs out there to show you how, but here it's what I do:

First of all, go to the WeTab Market (first icon you'll find on the desktop), Tools & Utilities section, and download the Firefox Internet browser. It will install itself inmediately. Open it and go to Oracle site and download jre-7u7-linux-i586.rpm. It will be stored by default in /home/<user>/Downloads.

Download also the RootShell app, so you can open a terminal window and enter the command line world.



Write and insert password when asked:
#cd Downloads
#sudo rpm -ivh jre-7u7-linux-i586.rpm

Then the whole package will be installed in /usr/java/jre1.7.0_07. Also the links /usr/java/default and /usr/java/latest will be added. Ignore a few errors shown at the end of the process.

To add the JRE to the path, edit the file:
#sudo gedit java.sh

add the following:
#!/bin/sh
export PATH=/usr/java/default/bin:$PATH
export JAVA_HOME=/usr/java/default


save and close it, and install it:
#sudo install -m755 java.sh /etc/profile.d

Close the console, and you're ready to try any Java or JavaFX application.

SlideLockSample. First try

In my desktop I create a sample class called SlideLockSample1, which I compile and build and then I copy the jar file to the WeTab, with an USB flash drive, for instance. This is part of the start method:

    @Override
    public void start(Stage primaryStage) {
        final Label lbl=new Label("Locked!");
        lbl.styleProperty().set("-fx-text-fill: red;-fx-font: 1.5em \"Arial Bold\";");
                    
        SlideLock slideLock = new SlideLock("slide to JavaFX");
        slideLock.setBackgroundVisible(true);
        slideLock.lockedProperty().addListener(new ChangeListener< boolean >() {

            @Override
            public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    lbl.styleProperty().set("-fx-text-fill: blue");
                    lbl.textProperty().set("Unlocked!!!!");
                } else {
                    lbl.styleProperty().set("-fx-text-fill: red");
                    lbl.textProperty().set("Locked!");
                }
            }
        });
    }


Here you have the code and the jar from JFXtras project, with the very last commits.

And here you have a small video taken with my iPhone showing how I slide the control with my finger, after starting the project with java -jar SlideLockSample1.jar in the console.

I was excited to see the touch event working... until I realized it was in fact a simple mouse event. To test that I commented first the touch events in addHandlers() method in SlideLockSkin class:

private void addHandlers() {
        // MouseEvents
        setOnMousePressed(mouseHandler);
        setOnMouseDragged(mouseHandler);
        setOnMouseReleased(mouseHandler);
        // TouchEvents
        setOnTouchPressed(touchHandler);
        setOnTouchMoved(touchHandler);
        setOnTouchReleased(touchHandler);
    }

And ran the jar again, working as it did the first time. So then I uncommented touch events and commented mouse events. Guess what? It didn't work.

So my multitouch tablet was only handling single touch events as pure mouse events. I found googling that with the last touchscreen firmware (which I had) the multitouch was supported <just> in the browser, the maps and the Picture Viewer app. I tested this one, and it really had zoom, rotation, scrolling  gestures working. You can find it in the WeTab Market, Entertainment and Multimedia section.

What about other applications? Nothing at all.

Quite dissapointed I looked for other options. I found a really good WeTab blog here, and following his post about multitouch I took the plunge and installed Ubuntu in my WeTab, now with instructions from this other post.

Partitioning the WeTab

As I wanted to keep the WeTab OS, I had to partition the 32 GB disk. For that I used GParted software, which I could download from here.  

Shortly, from Windows OS, you have to unzip the content to the USB flash drive, open a command line window with administrator privileges. Then let's say USB drive is H:, type H:cd utils/win32, and run makeboot.bat to install a hidden file and make the flash drive bootable.

Then comes the tricky part... Plug in the flash drive in the WeTab, turn it on and as soon as you see the blue led in the top left corner light up, press both the power-button and the quicktouch orange button (top left corner) together for approximately 1 second, and release them. GParted Live should boot up from the USB flash drive.

Then use the quicktouch orange button on the upper left corner of the WeTab to browse through the Boot menu for Gparted. One short touch switches between options. Select "other modes of Gparted",
hold down a second on the quicktouch button, it selects the entry. Then choose "Run from RAM or memory". It takes a while to load, but at the end it shows a graphical application with all the partitions. Select the sda3 partition (the biggest one) and then Resize. I resized mine to allow 8 GB for Ubuntu. Select the unasigned space and create a new partition with ext3 format (sda4).

Installing Ubuntu

Download last Ubuntu version, 12.04, from here. To create a bootable USB drive from Windows, also download and install this. Then open it, locate the ubuntu image, select the flash drive and click on Create.

Now you have to plug in the flash drive in the WeTab, turn it on, press both power and quicktouch buttons for a second, and when the installer boots up, follow the process to install Ubuntu. At the end of the process, you should modify the WeTab booting options to select WeTab OS or Ubuntu.

Finally, in order to allow multitouch events, following Etheros blog, I opened a console and typed:
sudo add-apt-repository ppa:utouch-team/utouch

Guess what? The repository was gone... You can check it here, the page is not found.

That's one of the thing I fear from Linux... you're always depending on some team's work, which suddenly may disappear.

Belive me I tried other options from the repository, but nothing worked, although I must say probably I missed something as clearly I am not a Linux expert.

Anyway, I installed Java, pretty much as described before, and it happened the same, the touch event was treated like a mouse event. No multitouch at all, as expected.

So I took another approach...

Installing Windows 8

A friend of mine told me about the native support of Windows 7 and Windows 8 for touchscreens, so I decided to give it a try, as the WeTab is x86 based.

So I downloaded Windows 8 Release Preview from here, and unzipped it to the USB flash drive.  Plugged in the flash drive in the WeTab, turned it on, pressed both power and quicktouch buttons for a second, and followed the process to install Windows 8 (in my case, in the sda4 partition, overwritting Ubuntu). At the end of the process the new interface of Windows was ready, with multitouch gestures support.

I won't go any further with that... Just two things: I ran out space! The installation took the 8 GB and left 90 MB free!! And there were no way to boot from WeTab anymore...

Well, I had to start all over again... Booting with GParted Live, I deleted sda4 partition, resized sda3 to have at least 16 GB of free space, created a new NTFS partition and installed again Windows 8.

To allow booting from WeTab OS, I found the EasyBCD software here, which allows to create a bootable menu from Windows. Just add a new Entry, select Bios Extender and install PLOP.

Java and JavaFx, second try

Downloading and installing JRE as usually in Windows, I was finally ready to test JavaFX multitouch gestures!

To end this long post, let me just show you two samples I've tested from JFXtras project.  Also you may have a look at this tutorial from JavaFX documentation.

This is the SlideLock Sample video I've taken with my iPhone, so excuse me for its bad quality:

You can see real touch events in action. Really smooth. Here you may find the surce code for the SlideLockSample2. Great job Carl!!

And this is the Gauge Radial Sample video:


Now, you can see rotate gestures in action. For the code, just look at Gerrit's blog here. As always, great work, Gerrit. 

Well, sure you've noticed in the video the slow performance of the radial... Don't blame Gerrit... As he pointed out, the issue here is hardware vs software acceleration.

If you look at JavaFX 2.2 system requirements here you'll read that for JavaFX applications to take advantage of the new hardware acceleration pipeline provided by JavaFX, your system must feature one of a wide range of GPUs currently available in the market. For Intel graphics cards, as mine, you need at least GMA 4500MHD and GMA HD. 

But the WeTab card is GMA 3150, so that's why if the system does not support hardware acceleration, then JavaFX uses the Java2D software pipeline. Here the Atom N450 does its best...

Conclusions

First of all, excuse me for this long post. I've tried to talk about many things... You can always skip most of them and go to a part of your interest.

Second, you can see there's plenty of room to experiment here... Please, feel free to share any experience!

Finally, about multitouch gestures, although they look astonishing, there's lot to do in many devices and OSes. In this early stages, you should find the proper combination of hardware and software. A bad choice can ruin your work. That's where tablets like the WeTab have their pros, easily you can make a light demo of your projects. 

And it's for another discussion what happens with Java Embedded and JavaFX for more limited architectures like ARM platforms, with devices like Raspberry Pi.


Friday, September 14, 2012

A JavaFX weekly scheduler

Hi there!

This post pretends to be a detailed tutorial of how you can use custom JavaFX controls to make a nice UI, taking advantage of the JavaFX Scene Builder

As custom control I'll use a slightly modified DoubleSlider control, which originally was developed by Altuğ Uzunali by modifying the Slider Control in OpenJFX project. You can see his blog here, and download his code here. Nice work, Altuğ!

As UI I'll try to develop a weekly scheduler for working or bussines hours, using one double slider to set beginning and ending hours for each day of the week.

Here you can see in advance the result. If you find it interesting and want to learn how can you do it yourself, please, keep reading!



Step 1. Create the project

For starters, let's make a new project in NetBeans 7.2, selecting the JavaFX FXML Application type. Let's call the project WeeklySchedulerFX. Also, the FXML file will be called scheduler:

At end of the wizard three files will be created:

Step 2. Create the UI

By double clicking in scheduler.fxml the JavaFX Scene Builder will be open. Now we have to delete what it contains by default (a button and a label) and keep the AnchorPane, and start designing our own scene, by first resizing it to 600x450 dragging it or in the Layout panel, setting Pref width to 600 and Pref height to 450.

Now, let's add the label for the title. It has the following settings: 

Properties panel: 
  • Text: Weekly Scheduler  - Working Hours
  • Alignment: CENTER
  • Effect: DropShadow, spread 0.20, color #956e07,
  • Font: System 16px (Bold)
 Layout panel:
  • AnchorPane Constraints: Top 30, Right 20, Left 20.

Now, we need 7 rows for the doubleSlider controls, and a place to locate the labels (day and range of hours selected), so let's use a GridPane with 7 rows and 2 columns. In the Properties panel, first of all, we set the fx:id for this control, so we can use it in our code later.

Properties panel: 
  • fx:id: grid
  • Alignment: CENTER
  • Vgap: 2
 Layout panel:
  • AnchorPane Constraints: Top 70, Right 20, Bottom 20, Left 20.
  • add Row[3], Row [4], Row [5] and Row [6]
  • Column [0], Pref Width=100, Hgrow NEVER
  • Column [1], Pref Width=460, Max Width: USE_COMPUTED_SIZE, Hgrow ALWAYS

This is what we have for now:

Although the GridPane is a container and we could just add the controls to the specified row and column, we can add to each cell another container, so we can insert later on the custom control or other controls from code, just looping through the gridPane children. 

Let's add a VBox to each cell, and set for all of them Alignment: CENTER. For the first column, also we set Spacing: 5 and Padding 5-5-5-5. For the second column, set Padding: 10-10-10-10.

Now, in the first column, we can add a label with the day of the week. For the first one:

Properties panel: 
  • Text: MONDAY
  • Effect: DropShadow
and so on. 

That's all for the Java Scenic Builder. We can close it and move on to the code part. You should have something like this:

Step 3. DoubleSlider custom control

First of all, we download Altuğ Uzunali's code, and in our project we create a package called doubleSlider and insert DoubleSlider.java, DoubleSliderBehavior.java, DoubleSliderSkin.java and double_slider.css files there. Make sure you change the package declaration to weeklyschedulerfx.doubleSlider  in the java files, and set the -fx-skin property:

.double-slider
{
    -fx-skin: "weeklyschedulefx.doubleSlider.DoubleSliderSkin";
}

in the css one. 

Now we are going to make a few changes in his code. (For shortness sake I won't comment here every change I make, but there are very few; please have a look at the code you can download below)

In order to display time formats instead of numbers in the ticks of the slider, in DoubleSliderSkin class, in the setShowTickMarks method, insert this code just after the tickLine.setMinorTickCount line:


if (doubleSlider.getLabelFormatter() != null){
    tickLine.setTickLabelFormatter(new StringConverter(){

        @Override
        public String toString(Number object) {
            return getSkinnable().getLabelFormatter().toString((Double)object);
        }

        @Override
        public Number fromString(String string) {
            return getSkinnable().getLabelFormatter().fromString(string);
        }
    });
}


Let's add now a node to the control in order to highlight the range of hours between the two thumbs. In DoubleSliderSkin class, declare it:


private StackPane range;


Initialize it (in initialize method) and add it with the other stackpanes (both in initialize and setShowTickMarks methods) to the scene: 

range = new StackPane();
range.getStyleClass().setAll("range");

getChildren().clear();
getChildren().addAll(track, thumb1, thumb2, range);


Now we have to set the range layout. Every time the thumb's layout change the method positionThumb is called. So there insert the following code:

if(horizontal){
    range.setLayoutX(lx1+thumbWidth/2);
    range.setLayoutY(track.getLayoutY());
    range.setPrefWidth(lx2-lx1);
    range.resize(lx2-lx1, track.getHeight());
} else{
    range.setLayoutX(track.getLayoutX());
    range.setLayoutY(ly2+3*thumbHeight/4);
    range.setPrefHeight(Math.abs(ly2-ly1+thumbHeight/2));
    range.resize(track.getWidth(),Math.abs(ly2-ly1+thumbHeight/2));
}

In the layoutChildren method call positionThumb after the track is resized and relocated, not before:

// layout track
track.resizeRelocate(trackStart - trackRadius, trackTop , trackLength + trackRadius + trackRadius, trackHeight);
// layout thumbs and range
positionThumb();

Finally we add the styling properties to the css file (a few more are in the file below):

.double-slider Text {
    -fx-text-fill: #303030;
}

.double-slider .range {
    -fx-base: #956e07;
    -fx-background-color: -fx-base,
        derive(-fx-base,-22%),
        linear-gradient(to bottom, derive(-fx-base,-15.5%), derive(-fx-base,34%) 30%, derive(-fx-base,68%));
    -fx-background-insets: 1 0 -1 0, 0, 1;
    -fx-padding: 0.208333em; /* 2.5 */
}

.double-slider .axis {
    -fx-tick-label-fill: #303030;
}

And that's it!

Step 4. Application setup

Back to our application, we need to add to the Stylesheets the custom control one, and set the stage title:


Scene scene = new Scene(root);
scene.getStylesheets().addAll(DoubleSlider.class.getResource("double_slider.css").toExternalForm());

stage.setScene(scene);
stage.setTitle("JavaFX Tutorial");

Step 5. The Controller

First of all, we add the GridPane container defined in the fxml file:

@FXML
private GridPane grid;

In the initialize method now we loop through the grid children. In the first seven nodes, related to the first column, we add to each VBox a new label which will show the selected range in the format [00:00 - 24:00]. 

final Label[] rangeLabel=new Label[7];
int cont=0;
for(Node n:grid.getChildren()){
    if(n instanceof VBox){
        VBox cell=(VBox)n;
        if(cont<7){                   
            rangeLabel[cont]=new Label();
            rangeLabel[cont].textProperty().set("[00:00 - 00:00]");
            cell.getChildren().add(rangeLabel[cont]);                    
        } 
        ...

In the last seven nodes, we add the doubleSlider controls. We want to show a range of hours and half hours, so we have 2*24+ 1 tick marks, and that's why we set the min value to 0 and the max value to 48. 

Also, the unit distance between major tick marks is set to 2 (hours), and the number of minor ticks between any two major ticks is specified as 1 (half hour). The setSnapToTicks method is set to true to keep the slider's value aligned with the tick marks.
 
...
else {
    final int day=cont-7;

    final DoubleSlider doubleSlider1 = new DoubleSlider();
    doubleSlider1.setPrefWidth(300);                
    doubleSlider1.setShowTickMarks(true);
    doubleSlider1.setShowTickLabels(true);
    doubleSlider1.setMajorTickUnit(2);
    doubleSlider1.setMinorTickCount(1);
    doubleSlider1.setSnapToTicks(true);
    doubleSlider1.setMin(0);
    doubleSlider1.setMax(48);
    doubleSlider1.setLabelFormatter(new StringConverter(){              
        @Override
        public String toString(Double object) {
            if(object==null){
                return null;
            }
            return toTime(object.doubleValue());
        }

        @Override
        public Double fromString(String string) {
            return (string!=null?new Double(string):new Double(0));
        }
    });
    doubleSlider1.value1Property().addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue arg0,
                        Number arg1, Number arg2) {
            rangeLabel[day].textProperty().set("["+toTime(arg2.intValue())+" - "+toTime(doubleSlider1.getValue2())+"]");
        }
    });
    doubleSlider1.value2Property().addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue arg0,
                        Number arg1, Number arg2) {
            rangeLabel[day].textProperty().set("["+toTime(doubleSlider1.getValue1())+" - "+toTime(arg2.intValue())+"]");
        }
    });
}

You can see that we set the labelFormatter to map the double value of the thumb position to a time string. For this, a simple string formatter is used:
 
private String toTime(double value){
    return String.format("%02d:%02d", (int)(value/2), (int)(30*(value%2)));
}

Also, we add a valueProperty() listener for each thumb so we can reflect their position changes in the proper rangeLabel.

Finally, we decorate the cells, taking here the advices of  Jasper Potts in his the excellent post Styling FX Buttons with CSS:
 
cell.setStyle("-fx-background-color: #ecebe9,rgba(0,0,0,0.05),linear-gradient(#dcca8a, #c7a740),"
        + "linear-gradient(#f9f2d6 0%, #f4e5bc 20%, #e6c75d 80%, #e2c045 100%),"
        + "linear-gradient(#f6ebbe, #e6c34d);"
        + "-fx-background-radius: 4;-fx-background-insets: 0,2 2 1 2,2,3,4;");

End

And this is pretty much everything you need to do to have a cool control and a really nice UI up and running in a few hours! Even minutes if you grab the code here!

Now it's up to you to make any changes you may have missed here.

Please let me know if you have any comment or suggestion. They would be much appreciated!