The state of JVM desktop frameworks: SWT
Tags: kotlin, swt, gui, desktop
Published: 2021-01-24 17:28:21

This series is dedicated to the state of JVM desktop frameworks. After having had a look at Swing the previous week, this post focuses on the Standard Widget Toolkit.

## What is SWT?

<abbr title="Standard Widget Toolkit">SWT</abbr> originates from [Eclipse project](https://www.eclipse.org/eclipseide/), an <abbr title="Integrated Development Environment">IDE</abbr>. For Eclipse, the developers built a dedicated framework to build their graphic components upon. Swing and SWT have widely different designs. Swing implements the drawing of widgets in Java from scratch. On the opposite, SWT is a thin wrapper API that relies on native graphic objects. This has two main benefits:

1. Widgets look native to the platform
2. Rendering is faster

## SWT APIs

There's one guiding principle behind SWT: because it depends on native graphic objects, every component requires a "parent" object as its first parameter. The parent is the object the child will be drawn onto. Every SWT component's constructor takes the parent as its first argument.

![Overview of the SWT API](https://dev-to-uploads.s3.amazonaws.com/i/ukhmyy3g4isn1fbsfxdn.png)

## Fun with SWT

SWT has some peculiarities, most of them related to its design based on system libraries.

### Native dependency

SWT provides a JAR for each mainstream operating system _e.g._ Windows, Mac OSX, etc. For example, this is the Maven dependency for my laptop:

```xml
<dependency>
  <groupId>org.eclipse.platform</groupId>
  <artifactId>org.eclipse.swt.cocoa.macosx.x86_64</artifactId> <!--1-->
  <version>3.114.100</version>
  <scope>runtime</scope>                                       <!--2-->
</dependency>
```

1. JAR coordinates are platform-dependent. It contains the required native libraries in the form of JNI bindings.
2. The JAR is only required at runtime

![Structure of the SWT JAR on Mac OSX](https://dev-to-uploads.s3.amazonaws.com/i/twgechqfrhqaeqnlyd3w.jpg)

### SWT event control loop

Swing provides an event control loop out-of-the-box. This is not the case with SWT. We need to copy-paste the following code into each of our applications:

```kotlin
val display = Display()                           // 1
val shell = Shell(display)                        // 2
shell.open()                                      // 3
while (!shell.isDisposed) {                       // 4
  if (!display.readAndDispatch()) display.sleep() // 5
}
display.dispose()                                 // 6
```

1. Bridge between SWT and the OS
2. Create the top-level window
3. Display it
4. While the system native resources of the window have not been released
5. Handle queued events. If nothing needs to be done... do nothing
6. Free all system native resources

### No-arg constructors

Both windows and dialogs are represented as `Shell` instances in SWT. The top-level window requires no parent and thus `Shell` offers a no-arg constructor. But since `Shell` is a graphical control, all its parent classes do also offer such a constructor. Those constructors have an empty body and calling them doesn't do anything.

### Component creation order

The order in which components are instantiated on a parent is the order in which they will be added to the layout of that parent. If you need to decouple them, you need to be creative _e.g._ wrap the call to the constructor in a lambda.

Here's an SWT sample displaying a label, a text field, and a button in this order:

```kotlin
val label = Label(shell, SWT.LEFT)
val text = Text(parent, SWT.SINGLE or SWT.LEFT or SWT.BORDER)
val button = Button(shell, SWT.PUSH)
```

### Styling

As seen in the previous snippet, the styling of widgets happens during their instantiation. Those styles are coded in the `SWT` class in the form of style bits:

* `LEAD = 1 << 14`
* `LEFT = LEAD`
* `SINGLE = 1 << 2`
* `BORDER = 1 << 11`
* `PUSH = 1 << 3`
* etc.

### Circular dependency

Note that the constructor of `Control` takes a `Composite` instance, which itself is a subclass of `Control`. This circular dependency is bound to within the same package.

## Displaying tabular data

SWT concerns itself only with the widgets and their rendering. As opposed to Swing and JavaFX, it has no concept of _data model_: you need to manage the data yourself. It's manageable for 0-D data _e.g._ text fields and even for 1-D data _e.g._ list boxes. For 2-D data _i.e._ tables, it's a lot of trouble.

For this reason, most graphic frameworks introduce a model abstraction between the component and the data it manages. For example, Swing has `JTable` and a `TableModel`.

Eclipse delivers the [JFace](https://wiki.eclipse.org/JFace) library, which provides a data model abstraction over the SWT API among others. For example, for tables, JFace has the `TableViewer` class. At its core, every JFace viewer class wraps an SWT control.

![JFace SWT integration](https://dev-to-uploads.s3.amazonaws.com/i/bhpszvsm5ro5mmtsvai2.png)

The wrapping applies at a deep level: SWT's `TableColumn` is wrapped by JFace's `TableColumnViewer`.

The `Viewer` class has a rich type hierarchy to handle data of different dimensions. `IStructuredContentProvider` provides multiple rows of data such as those found in tables. Because the API was designed before generics, there's a runtime check at the `StructuredViewer` level to verify the type of the set `IContentProvider`. Moreover, `StructuredViewer` also provides sorting, filtering, and "decorating" capabilities.

![JFace TableViewer capabilities](https://dev-to-uploads.s3.amazonaws.com/i/ve5029uan6vmcl81iw9a.png)

Note that there's a library that manages two-way data bindings between the model and the control: [JFace Data Binding](https://wiki.eclipse.org/JFace_Data_Binding). I couldn't find a compatible version though.

## Conclusion

There's no denying SWT success. Not only Eclipse uses it, but [some software also do](https://en.wikipedia.org/wiki/Standard_Widget_Toolkit#Uses).


SWT provides a fully native look-and-feel GUI. On the good side, it means that applications behave natively. On the flip side, there's a cost associated with this capability though:

1. A dependency to the platform library, which breaks the _Compile Once Run Anywhere_ promise
2. Runtime checks instead of compile-time checks because of the lack of generics
3. For those two reasons above, the API feels unwieldy at times

Thanks a lot to [Benjamin Muskalla](https://twitter.com/bmuskalla) for his offer to review this post.

The complete source code for this post can be found on [Github](https://github.com/ajavageek/renamer-swt) in Maven format.

**To go further:**

* [SWT: The Standard Widget Toolkit](https://www.eclipse.org/swt/)
* [JFace](https://wiki.eclipse.org/JFace)
* [JFace Data Binding](https://wiki.eclipse.org/JFace_Data_Binding)
* [Development of Eclipse RCP applications](https://www.vogella.com/tutorials/eclipse.html)

_Originally published at [A Java Geek](https://blog.frankel.ch/state-jvm-desktop-frameworks/3/) on January 24<sup>th</sup> 2021_