Changes in this year…

 

Recent news

A lot of time passed since the last post here, so i will tell about all of them right now.

But before that i want to say thanks to everyone who helped me to improve WebLaF project and push it toward the right direction!  Was it a small feedback on some specific component, feature requests or bug reports, some interesting new ideas, translations or something else – i really appreciate all the help i got from you guys. Thanks again!

And i am looking forward to get even more feedback on this project as soon as it moves to the next stable stage and will get more awesome stuff included! 😉

Anyway, let’s leave the lyrics and move on…

GitHub and project changes

I spent a lot of time to improve project this year and i have tried a lot of tools.
There will be more changes coming but i am really satisfied with the work done.

Thanks to GitHub everyone can implement some custom feature into WebLaF sources and make a pull requests so i can quickly view it and merge into the main WebLaF branch. You can read more about making pull requests in GitHub help here. So don’t hesitate to make a pull request if you really want some feature included directly in WebLaF in a short time!

GitHub also offers a good way to handle various issue requests, so i would really appreciate if you post them here – https://github.com/mgarin/weblaf/issues. WebLaF forum will be more like Q&A section, but you can still use it if you like that way of communication more.

By the way GoogleCode project is now deprecated so i recommend you to get newer project/sources version from GitHub if you were still using the one from GoogleCode SVN.

Shortly about other changes with the project:
1. I have improved ANT build and added some more build options
2. Artifacts names were changed to make them brief and understandable
3. A lot of changes done to classes structure, code style and used libraries

Now let’s move on to major changes made this year…
(you will find more information under the cut)

File chooser UI implementation

Finally i have finished the most complex Swing UI – WebFileChooserUI is now available and fully functional. WebFileChooser is now a simple extension for JFileChooser which allows to use some additional UI features directly, without accessing the UI. Old versions of WebFileChooser and WebFileChooserPanel were removed since they are not needed anymore.

So here are a few screens of the new file chooser implementation:

File open = WebFileChooser.showOpenDialog ();

Single file selection

Single file selection

List<File> multiOpen = WebFileChooser.showMultiOpenDialog ();

Multiply files selection

Multiply files selection

File save = WebFileChooser.showSaveDialog ();

File save location selection

This implementation properly supports filtering, file saving dialog and some other Swing JFileChooser features out of the box. I will be also adding more features into file chooser like more view options, files view customization, OS-dependant features and more.

Base classes you might want to look into:
https://github.com/mgarin/weblaf/blob/master/src/com/alee/laf/filechooser/WebFileChooser.java
https://github.com/mgarin/weblaf/blob/master/src/com/alee/laf/filechooser/WebFileChooserUI.java
https://github.com/mgarin/weblaf/blob/master/src/com/alee/laf/filechooser/WebFileChooserPanel.java

Asynchronous tree

I have added this component a long time ago but it wasn’t complete and had no documentation or any examples to make it usable. Now i have improved it, fixed some bugs and added documentation and much more code features to make it worth your attention! :)

So here it is, a simple implementation of a Swing JTree with asynchronous childs loading – WebAsyncTree!

Let’s go straight to a simple example of its usage.
First of all we need a custom tree node based on AsyncUniqueNode class:

public static class MyNode extends AsyncUniqueNode
{
    public MyNode ( Object userObject )
    {
        super ( userObject );
    }
}

AsyncUniqueNode provides base methods for WebAsyncTree so it can function properly.
Now we will need a specific data model – AsyncTreeDataProvider:

AsyncTreeDataProvider dataProvider = new AsyncTreeDataProvider ()
{
    public AsyncUniqueNode getRoot ()
    {
        // Custom tree root
        return new MyNode ( "Root" );
    }

    public List getChilds ( AsyncUniqueNode node )
    {
        // Simply emulating heavy childs loading method that takes from 1 to 3 seconds to process
        ThreadUtils.sleepSafely ( MathUtils.random ( 1000, 3000 ) );
        return Arrays.asList ( new MyNode ( "Node1" ), new MyNode ( "Node2" ), new MyNode ( "Node3" ) );
    }

    public boolean isLeaf ( AsyncUniqueNode node )
    {
        // Return false so that every node can be expanded futher
        return false;
    }
};

This is the core of asynchronous tree – this data provider will be used by tree model to retrieve nodes which are not yet loaded. Thats it! You don’t need to bother about anything else – simply pass that data provider to WebAsyncTree and simply watch how it will gather the structure and load childs without blocking the UI:

WebAsyncTree asyncTree = new WebAsyncTree ( dataProvider );
TestFrame.show ( new WebScrollPane ( asyncTree ) );

This is how it will look like:

WebAsyncTree

By the way, WebFileTree which is used in file chooser also based on WebAsyncTree and has a lot of interesting methods to work with its structure.

You can read more about asynchronous tree here: http://weblookandfeel.com/forum/viewtopic.php?f=6&t=131

Color chooser UI implementation

This one also got implementation this year.
It is simple but yet stylish and useful:

public class ColorChooser
{
    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();
        Color color = WebColorChooser.showDialog ( null,Color.WHITE );
    }
}

WebColorChooser

In future some more changes awaits this component.

WebAccordion

Accordion component was introduced at the beginning of this year. It is based on WebCollapsiblePane but has a lot of its own features.

public class AccordionExample
{
    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();

        WebAccordion accordion = new WebAccordion ( AccordionStyle.separated );
        accordion.setMultiplySelectionAllowed ( false );
        accordion.setGap ( 5 );
        accordion.addPane ( WebLookAndFeel.getIcon ( 16 ), "Pane 1", createContent () );
        accordion.addPane ( WebLookAndFeel.getIcon ( 16 ), "Pane 2", createContent () );
        accordion.addPane ( WebLookAndFeel.getIcon ( 16 ), "Pane 3", createContent () );
        TestFrame.show ( accordion, 5 );
    }

    private static Component createContent ()
    {
        return  new WebTextArea ( "One\n" + "Two\n" + "Three" );
    }
}

hostingkartinok_com_4853225425359147067

This is just a demonstration of WebAccordion separate style mode with vertical panes orientation.
You can check the WebAccordion source code and try out other awesome features it offers!

Custom components

Some other fancy components were added this year, let’s just take a look at some of them quickly…

1. WebSwitch – something like modern phones on/off switch controls

public class WebSwitchExample
{
    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();
        TestFrame.show ( new CenterPanel ( new WebSwitch () ), 50 );
    }
}

hostingkartinok_com_6628849538827938441

2. WebStepProgress – this one is an unordinary one, some kind of progress bar and slider mix

public class WebStepProgressExample
{
    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();

        final WebStepProgress stepProgress = new WebStepProgress ();
        stepProgress.setSteps ( "1", "2", "3", "4", "Finish!" );
        stepProgress.setSelectionMode ( WebStepProgress.PROGRESS_SELECTION );

        TestFrame.show ( stepProgress, 5 );
    }
}

hostingkartinok_com_4213836268290921822

3. WebColorChooserField – complex color chooser that offers pipette, color chooser dialog and text filed to input desired color

public class WebColorChooserFieldExample
{
    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();
        TestFrame.show ( new WebColorChooserField ( Color.WHITE ), 5 );
    }
}

hostingkartinok_com_5476989683671353951

And there is more, just check the demo application!

Translations

Thanks to those who posted translations WebLaF now has eight interface languages!

Here is the full list of supported languages:

  • en English
  • ru Russian
  • pl Polish
  • ar Arabic
  • es Spanish
  • fr French
  • pt Portuguese
  • de German

You can switch to any language using this simple line of code:

LanguageManager.setLanguage ( LanguageConstants.ENGLISH );

You can also pass language code instead of constant:

LanguageManager.setLanguage ( "en" );

LanguageManager will do the rest.

Advanced multi-language support for Swing components

I bet making a Swing application support multiply languages was a real headache for everyone who tried to. It was for me as well for a long time, that is why i ended up developing my own tool to translate Swing components. That was the beginning of LanguageManager tool.

The best way to show what LanguageManager can do is to translate something real, so let’s imagine we need to translate a small timer application:

public class TimerApp extends WebFrame
{
    private static final SimpleDateFormat sdf = new SimpleDateFormat ( "mm:ss.SSS" );

    private int lapCount = 1;
    private long timePassed = 0;
    private final WebTimer timer;

    public TimerApp ()
    {
        super ();

        final WebLabel timeTitle = new WebLabel ( "Time passed:" );
        timeTitle.setFontSize ( 15 );
        final WebLabel time = new WebLabel ( "Lap 1: 00:00.000" );
        time.setFontSize ( 20 );

        final WebLabel lastTime = new WebLabel ( "Last time: 00:00.000" );
        lastTime.setForeground ( Color.DARK_GRAY );

        final WebButton start = new WebButton ( "Start (#" + lapCount + ")" );
        start.addActionListener ( new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                if ( timer.isRunning () )
                {
                    timer.stop ();
                    lapCount++;
                    start.setText ( "Start (#" + lapCount + ")" );
                }
                else
                {
                    start.setText ( "Stop (#" + lapCount + ")" );
                    lastTime.setText ( "Last time: " + sdf.format ( new Date ( timePassed ) ) );
                    timePassed = 0;
                    timer.start ();
                }
            }
        } );

        timer = new WebTimer ( 33, new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                timePassed += 33;
                time.setText ( "Lap " + lapCount + ": " + sdf.format ( new Date ( timePassed ) ) );
            }
        } );

        final WebPanel container = new WebPanel ( new VerticalFlowLayout ( 0, 5 ) );
        container.setMargin ( 5 );
        container.add ( timeTitle );
        container.add ( time );
        container.add ( start );
        container.add ( lastTime );
        getContentPane ().add ( container );

        setDefaultCloseOperation ( WebFrame.EXIT_ON_CLOSE );
        pack ();
    }

    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();
        new TimerApp ().setVisible ( true );
    }
}

So this is how it looks like:

Translation example

If you look at the source code you will see that some components have variables put right into their text and they are getting updated a lot. That is the hardest thing to handle when you want that component to get translated.

Now let’s have a look at the translation file for this example (with just two languages to make it simple):

<Dictionary name="Timer language" prefix="timer" author="mgarin" creationDate="13.08.2013">
    <record key="time.title">
        <value lang="en">Time passed:</value>
        <value lang="ru">Прошедшее время:</value>
    </record>
    <record key="time">
        <value lang="en">Lap %s: %s</value>
        <value lang="ru">Круг %s: %s</value>
    </record>
    <record key="last.time">
        <value lang="en">Last time: %s</value>
        <value lang="ru">Последнее время: %s</value>
    </record>
    <record key="start">
        <value lang="en">Start (%s)</value>
        <value lang="ru">Запустить (%s)</value>
    </record>
    <record key="stop">
        <value lang="en">Stop (%s)</value>
        <value lang="ru">Остановить (%s)</value>
    </record>
</Dictionary>

As you can see i have included special pointers (%s) which will be replaced by LanguageManager when translation occurs. LanguageManager uses simple “String.format(…)” method to form final language values which will be set into component, so you are free to use any kind of strings which java.util.Formatter will understand and parse properly.

The last step is to pass proper data for the specified language key for each component, let’s do it:

public class TimerApp extends WebFrame
{
    private static final SimpleDateFormat sdf = new SimpleDateFormat ( "mm:ss.SSS" );

    private int lapCount = 1;
    private long timePassed = 0;
    private final WebTimer timer;

    public TimerApp ()
    {
        super ();

        LanguageManager.addDictionary ( TimerApp.class, "language.xml" );

        final WebLabel timeTitle = new WebLabel ();
        timeTitle.setFontSize ( 15 );
        timeTitle.setLanguage ( "timer.time.title" );
        final WebLabel time = new WebLabel ();
        time.setFontSize ( 20 );
        time.setLanguage ( "timer.time", lapCount, formatTime ( 0 ) );

        final WebLabel lastTime = new WebLabel ();
        lastTime.setLanguage ( "timer.last.time", formatTime ( 0 ) );
        lastTime.setForeground ( Color.DARK_GRAY );

        final WebButton start = new WebButton ();
        start.setLanguage ( "timer.start", lapCount );
        start.addActionListener ( new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                if ( timer.isRunning () )
                {
                    timer.stop ();
                    lapCount++;
                    start.setLanguage ( "timer.start", lapCount );
                }
                else
                {
                    start.setLanguage ( "timer.stop", lapCount );
                    lastTime.setLanguage ( "timer.last.time", formatTime ( timePassed ) );
                    timePassed = 0;
                    timer.start ();
                }
            }
        } );

        timer = new WebTimer ( 33, new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                timePassed += 33;
                time.setLanguage ( "timer.time", lapCount, formatTime ( timePassed ) );
            }
        } );

        final WebPanel container = new WebPanel ( new VerticalFlowLayout ( 0, 5 ) );
        container.setMargin ( 5 );
        container.add ( timeTitle );
        container.add ( time );
        container.add ( start );
        container.add ( lastTime );
        getContentPane ().add ( container );

        setDefaultCloseOperation ( WebFrame.EXIT_ON_CLOSE );
        pack ();
    }

    private String formatTime ( long time )
    {
        return sdf.format ( new Date () );
    }

    public static void main ( String[] args )
    {
        WebLookAndFeel.install ();
        new TimerApp ().setVisible ( true );
    }
}

As you can see – our example got just a few lines longer, but it is already fully translated. And the best thing about it is that we do not need to do anything at all in case language will get changed – LanguageManager will automatically update all registered components!

A few notes:

  1. As you can see i am passing additional data into “setLanguage” method for those components who uses complex translation which includes some variables – that data will be used by formatter to form the final translation text.
  2. You can find “setLanguage” methods in almost any Web-component that has any kind of text that can be translated.
  3. Do not hesitate to use “setLanguage” method as frequently as you need – it is optimized and will quickly update the appropriate component (or group components) without causing any other changes and UI updates.
  4. You can create custom LanguageUpdater implementations to your own components translation support into LanguageManager. That will allow you to simplify their translation process and avoid some common translation mistakes.

Results

I didn’t have much time until last month but i still managed to add some features and close a lot of bugs and issues. And I think I will be able to add a lot more features i was going to add and complete a stable release this year, i feel it!

Anyway all i have done wouldn’t be possible without support, so thanks again to everyone who assisted with this library development! :)