From Swing to Jetpack Compose Desktop #2
Tags: java, kotlin, ux
Published: 2020-11-27 13:19:07

Welcome to the second post about my journey of transforming a Java Swing app to Jetpack Compose for Desktop. Today I will pick just one topic. That is, start working on it. If you recall the ui, we have a text field which shall contain the base directory to search for duplicate files:

![Entering the base directory](https://dev-to-uploads.s3.amazonaws.com/i/jrxaroqyqy739ct6lioy.png)

The idea is to enter a valid path and then click **Find**. Obviously the button should be active only if that condition is met. The composable `FirstRow` currently remembers one state:

```kotlin
val name = remember { mutableStateOf(TextFieldValue("")) }
```

So we can easily add the button code like this:

```kotlin
Button(
        onClick = {},
        modifier = Modifier.alignByBaseline(),
        enabled = File(name.value.text).isDirectory
) {
    Text("Find")
}
```

The text field works with the native clipboard, so if you happen to have the path as a string you can paste it with *Control-V* or *Cmd-V*. But that's not particularly desktop-py, is it? A key feature of Desktop operating systems is supporting multiple windows. So, we would want to pick the folder from the native file manager, right?

I am a Jetpack Compose for Desktop newbie and may well have missed this, but so far I have not seen drag and drop support. That's why I decided to do this on my own. Jetpack Compose for Desktop top-level windows can easily interact with Swing, so we can borrow the drag and drop capabilities from there. Let's see how this works in general:

```kotlin
val target = object : DropTarget() {
    @Synchronized
    override fun drop(evt: DropTargetDropEvent) {
        try {
            evt.acceptDrop(DnDConstants.ACTION_REFERENCE)
            val droppedFiles = evt
                    .transferable.getTransferData(
                            DataFlavor.javaFileListFlavor) as List<*>
            for (file in droppedFiles) {
                println((file as File).absolutePath)
            }
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }
}
AppManager.windows.first().window.contentPane.dropTarget = target
```

As the `DropTarget` is Swing stuff I will not elaborate on this. But please keep in mind that this code just prints the names of the dropped files. To get the text field updated, we will need to alter the `for` loop. More on this soon. But let's take a look at the last line first (no pun intended). `AppManager.windows` gives us a list of all windows of an application. TKDupeFinder has just one, the main window. So we can get this with `first()`. This is an instance of `androidx.compose.desktop.AppFrame`. `window` is a `ComposeWindow` which extends `JFrame`. And that is why we can access `contentPane` and set a drop target. 

Nice, isn't it? To conclude this session, here is how to update the text field. First, the slightly changed `main()` function:

```kotlin
fun main() {
    invokeLater {
        AppWindow(title = "TKDupeFinder",
                size = IntSize(600, 400)).show {
            TKDupeFinderContent()
        }
    }
}
```

`TKDupeFinderContent` is a composable. As you will see, it remembers the `name` and passes it to `FirstRow` (this is new, too). I do this because upon a drag I need to update `name`.

```kotlin
@Composable
fun TKDupeFinderContent() {
    val name = remember { mutableStateOf(TextFieldValue("")) }
    DesktopMaterialTheme {
        Column() {
            FirstRow(name)
            SecondRow()
            ThirdRow()
        }
    }
    val target = object : DropTarget() {
        @Synchronized
        override fun drop(evt: DropTargetDropEvent) {
            try {
                evt.acceptDrop(DnDConstants.ACTION_REFERENCE)
                val droppedFiles = evt
                        .transferable.getTransferData(
                                DataFlavor.javaFileListFlavor) as List<*>
                droppedFiles.first()?.let {
                    name.value = TextFieldValue((it as File).absolutePath)
                }
            } catch (ex: Exception) {
                ex.printStackTrace()
            }
        }
    }
    AppManager.windows.first().window.contentPane.dropTarget = target
}

```

My code assumes that only one file or folder is dragged onto the window. If there are more I just use the first one (`droppedFiles.first()`). The path (`absolutePath`) must be wrapped in a `TextFieldValue`. The following clip shows how this looks on macOS.

{% youtube 9wOMas9R6AM %}

Pretty cool, isn't it? The next post will cover the actual search for duplicates. So stay tuned. If you have missed the first part, you can read it [here](https://dev.to/tkuenneth/from-swing-to-jetpack-compose-desktop-1-12kf). The TKDupeFinder repo is on [GitHub](https://github.com/tkuenneth/TKDupeFinder).