@@ -2,18 +2,23 @@ package dev.paulee.ui.components
22
33import androidx.compose.foundation.layout.*
44import androidx.compose.material.*
5- import androidx.compose.runtime.Composable
5+ import androidx.compose.runtime.*
66import androidx.compose.ui.Modifier
7+ import androidx.compose.ui.awt.SwingPanel
78import androidx.compose.ui.unit.dp
89import androidx.compose.ui.window.AwtWindow
910import androidx.compose.ui.window.Dialog
1011import androidx.compose.ui.window.DialogProperties
12+ import dev.paulee.ui.Config
1113import dev.paulee.ui.LocalI18n
1214import dev.paulee.ui.SimpleTextField
1315import java.awt.FileDialog
1416import java.awt.Frame
17+ import java.io.File
1518import java.io.FilenameFilter
1619import java.nio.file.Path
20+ import javax.swing.JFileChooser
21+ import javax.swing.filechooser.FileNameExtensionFilter
1722
1823enum class DialogType {
1924 LOAD , SAVE
@@ -25,13 +30,28 @@ fun FileDialog(
2530 dialogType : DialogType = DialogType .LOAD ,
2631 extension : String? = null,
2732 onCloseRequest : (result: List <Path >) -> Unit ,
33+ ) {
34+ if (Config .useLegacyFileDialog) LegacyFileDialog (dialogType, extension, onCloseRequest)
35+ else SystemFileDialog (parent, dialogType, extension, onCloseRequest)
36+ }
37+
38+ @Composable
39+ fun SystemFileDialog (
40+ parent : Frame ? = null,
41+ dialogType : DialogType = DialogType .LOAD ,
42+ extension : String? = null,
43+ onCloseRequest : (result: List <Path >) -> Unit ,
2844) {
2945 val locale = LocalI18n .current
3046
3147 AwtWindow (
3248 create = {
3349 object :
34- FileDialog (parent, if (dialogType == DialogType .SAVE ) locale[" dialog.save" ] else locale[" dialog.open" ], dialogType.ordinal) {
50+ FileDialog (
51+ parent,
52+ if (dialogType == DialogType .SAVE ) locale[" dialog.save" ] else locale[" dialog.open" ],
53+ dialogType.ordinal
54+ ) {
3555
3656 init {
3757 isMultipleMode = dialogType == DialogType .LOAD
@@ -69,6 +89,110 @@ fun FileDialog(
6989 )
7090}
7191
92+ @Composable
93+ fun LegacyFileDialog (
94+ type : DialogType = DialogType .LOAD ,
95+ extension : String? = null,
96+ onCloseRequest : (result: List <Path >) -> Unit ,
97+ ) {
98+ val locale = LocalI18n .current
99+
100+ val title = if (type == DialogType .SAVE ) locale[" dialog.save" ] else locale[" dialog.open" ]
101+ var closeResult by remember { mutableStateOf<List <Path >? > (null ) }
102+
103+ closeResult?.let {
104+ LaunchedEffect (closeResult) { onCloseRequest(it) }
105+ return
106+ }
107+
108+ val normalizedExtension = extension?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
109+
110+ fun JFileChooser.configure (initial : Boolean = true) {
111+ dialogTitle = title
112+ dialogType = if (type == DialogType .SAVE ) JFileChooser .SAVE_DIALOG else JFileChooser .OPEN_DIALOG
113+ isMultiSelectionEnabled = type == DialogType .LOAD
114+ fileSelectionMode = JFileChooser .FILES_ONLY
115+
116+ normalizedExtension?.let { ext ->
117+ val extensionFilter = FileNameExtensionFilter (" *.$ext " , ext)
118+
119+ if (initial) {
120+ fileFilter = extensionFilter
121+ isAcceptAllFileFilterUsed = true
122+
123+ } else {
124+ val filter = fileFilter
125+
126+ if (filter !is FileNameExtensionFilter || filter.extensions.any { it.equals(ext, true ) }) {
127+ resetChoosableFileFilters()
128+
129+ fileFilter = extensionFilter
130+ isAcceptAllFileFilterUsed = true
131+ }
132+ }
133+ }
134+ }
135+
136+ Dialog (
137+ onDismissRequest = { closeResult = emptyList() },
138+ properties = DialogProperties (
139+ usePlatformDefaultWidth = false ,
140+ dismissOnBackPress = true ,
141+ dismissOnClickOutside = true
142+ )
143+ ) {
144+ Surface (
145+ modifier = Modifier .padding(16 .dp),
146+ shape = MaterialTheme .shapes.medium,
147+ elevation = 8 .dp
148+ ) {
149+ SwingPanel (
150+ modifier = Modifier .sizeIn(minWidth = 520 .dp, minHeight = 420 .dp),
151+ factory = {
152+ JFileChooser ().apply {
153+ configure()
154+
155+ if (type == DialogType .SAVE && selectedFile == null ) {
156+ val baseName = buildString {
157+ append(" untitled" )
158+ normalizedExtension?.let { append(' .' ).append(it) }
159+ }
160+
161+ selectedFile = File (currentDirectory, baseName)
162+ }
163+
164+ addActionListener { event ->
165+ val approved = event.actionCommand == JFileChooser .APPROVE_SELECTION
166+
167+ if (! approved) {
168+ closeResult = emptyList()
169+ return @addActionListener
170+ }
171+
172+ val files =
173+ if (isMultiSelectionEnabled) selectedFiles
174+ else selectedFile?.let { arrayOf(it) }.orEmpty()
175+
176+ closeResult = files.mapNotNull { file ->
177+ if (file == null ) return @mapNotNull null
178+
179+ if (type == DialogType .SAVE && normalizedExtension != null ) {
180+
181+ val withExt = if (file.extension == normalizedExtension) file
182+ else File (file.parentFile ? : currentDirectory, " ${file.name} .$normalizedExtension " )
183+
184+ withExt.toPath()
185+ } else file.toPath()
186+ }
187+ }
188+ }
189+ },
190+ update = { it.configure(false ) }
191+ )
192+ }
193+ }
194+ }
195+
72196@Composable
73197fun CustomInputDialog (
74198 title : String ,
0 commit comments