Skip to content

Commit 8d2a753

Browse files
committed
Rewriting the file chooser page in the import and export wizards in JavaFX
1 parent b7edbbc commit 8d2a753

File tree

8 files changed

+192
-461
lines changed

8 files changed

+192
-461
lines changed

ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ class PropertyPaneBuilderImpl(private val localizer: Localizer, private val grid
295295
}
296296

297297
val resultFile = fileChooser.showOpenDialog(null)
298-
option.value = resultFile
298+
option.set(resultFile, textField)
299299
resultFile?.let {
300300
textField.text = it.absolutePath
301301
}
@@ -306,8 +306,14 @@ class PropertyPaneBuilderImpl(private val localizer: Localizer, private val grid
306306
onClick = { onBrowse() },
307307
styleClass = "btn"
308308
)
309+
textField.text = option.value?.absolutePath ?: ""
309310
textField.id = option.id
310311
displayOptions?.editorStyles?.let(textField.styleClass::addAll)
312+
option.addWatcher {
313+
if (it.trigger != textField) {
314+
textField.text = option.value?.absolutePath ?: ""
315+
}
316+
}
311317
return textField
312318
// return HBox().apply {
313319
// HBox.setHgrow(textField, Priority.ALWAYS)

ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
package net.sourceforge.ganttproject.export
2020

2121
import biz.ganttproject.app.RootLocalizer
22+
import biz.ganttproject.core.option.FileExtensionFilter
2223
import biz.ganttproject.core.option.GPOptionGroup
24+
import biz.ganttproject.core.option.ObservableString
2325
import biz.ganttproject.storage.asLocalDocument
2426
import biz.ganttproject.storage.getDefaultLocalFolder
2527
import com.github.michaelbull.result.Err
@@ -49,7 +51,8 @@ internal class ExportFileChooserPage(
4951
myProject.document, uiFacade,
5052
fileChooserTitle = i18n.formatText("selectFileToExport"),
5153
fileChooserSelectionMode = JFileChooser.FILES_AND_DIRECTORIES,
52-
pageTitle = i18n.formatText("selectFileToExport")
54+
pageTitle = i18n.formatText("selectFileToExport"),
55+
errorMessage = ObservableString("", "")
5356
) {
5457
private val myWebPublishingGroup: GPOptionGroup = GPOptionGroup(
5558
"exporter.webPublishing", myState.publishInWebOption
@@ -60,18 +63,20 @@ internal class ExportFileChooserPage(
6063
proposeChosenFile = {
6164
proposeOutputFile(myProject, myState.exporter) ?: File(defaultFileName)
6265
}
63-
overwriteOption.addChangeValueListener { tryChosenFile(chooser.file) }
64-
selectedFileProperty.addListener { _, _, newValue ->
65-
myState.file = newValue
66+
fxFile.addWatcher {
67+
myState.file = it.newValue
6668
}
69+
// selectedFileProperty.addListener { _, _, newValue ->
70+
// myState.file = newValue
71+
// }
6772
}
6873

6974
override fun validateFile(file: File?): Result<File, String> {
7075
if (file == null) {
7176
return Err("File cannot be null")
7277
}
7378

74-
overwriteOption.getIsWritableProperty().set(false, this)
79+
fxOverwrite.isWritable.value = false
7580
if (!file.exists()) {
7681
val parent = file.getParentFile()
7782
if (!parent.exists()) {
@@ -101,8 +106,8 @@ internal class ExportFileChooserPage(
101106
)
102107
}
103108
} else {
104-
overwriteOption.getIsWritableProperty().set(true, this)
105-
if (!overwriteOption.isChecked()) {
109+
fxOverwrite.isWritable.value = true
110+
if (!fxOverwrite.value) {
106111
return Err(i18n.formatText("fileChooser.warning.fileExists"))
107112
}
108113
}
@@ -113,10 +118,7 @@ internal class ExportFileChooserPage(
113118
return myState.exporter.getCustomOptionsUI() ?: super.createSecondaryOptionsPanel()
114119
}
115120

116-
override fun createFileFilter(): FileFilter =
117-
ExtensionBasedFileFilter(
118-
myState.exporter.getFileNamePattern(), myState.exporter.getFileTypeDescription()
119-
)
121+
override fun createFileFilter(): FileExtensionFilter = FileExtensionFilter(myState.exporter.getFileTypeDescription(), listOf(myState.exporter.getFileNamePattern()))
120122

121123
override val optionGroups: List<GPOptionGroup>
122124
get() = listOf(myWebPublishingGroup) + (myState.exporter?.secondaryOptions ?: emptyList())

ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt

Lines changed: 134 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,31 @@
1818
*/
1919
package net.sourceforge.ganttproject.gui
2020

21+
//import biz.ganttproject.platform.getMeaningfulMessage
22+
//import biz.ganttproject.platform.getMeaningfulMessage
23+
import biz.ganttproject.app.PropertySheetBuilder
2124
import biz.ganttproject.app.RootLocalizer
22-
import biz.ganttproject.core.option.BooleanOption
23-
import biz.ganttproject.core.option.DefaultBooleanOption
24-
import biz.ganttproject.core.option.GPOptionGroup
25+
import biz.ganttproject.core.option.*
2526
import com.github.michaelbull.result.Result
2627
import com.github.michaelbull.result.onFailure
2728
import com.github.michaelbull.result.onSuccess
2829
import javafx.beans.property.SimpleObjectProperty
30+
import javafx.embed.swing.SwingNode
31+
import javafx.scene.Node
32+
import javafx.scene.control.Label
33+
import javafx.scene.layout.BorderPane
34+
import javafx.scene.layout.HBox
2935
import net.sourceforge.ganttproject.document.Document
3036
import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder
3137
import net.sourceforge.ganttproject.gui.projectwizard.WizardPage
3238
import org.osgi.service.prefs.Preferences
3339
import java.awt.BorderLayout
3440
import java.awt.Component
3541
import java.io.File
36-
import javax.swing.*
37-
import javax.swing.filechooser.FileFilter
42+
import javax.swing.BorderFactory
43+
import javax.swing.JFileChooser
44+
import javax.swing.JLabel
45+
import javax.swing.JPanel
3846

3947
/**
4048
* Base class for the file chooser pages in the Import and Export wizards.
@@ -44,39 +52,103 @@ abstract class FileChooserPageBase protected constructor(
4452
uiFacade: UIFacade?,
4553
val fileChooserTitle: String?,
4654
val pageTitle: String?,
47-
val fileChooserSelectionMode: Int = JFileChooser.FILES_ONLY
55+
val fileChooserSelectionMode: Int = JFileChooser.FILES_ONLY,
56+
val errorMessage: ObservableString
4857
) : WizardPage {
49-
protected val chooser: TextFieldAndFileChooserComponent = object : TextFieldAndFileChooserComponent(uiFacade, this.fileChooserTitle) {
50-
override fun onFileChosen(file: File?) {
51-
tryChosenFile(file)
58+
val fxFile = ObservableFile("file", null)
59+
private var fileFilter: FileExtensionFilter? = null
60+
set(value) {
61+
field = value
62+
if (value != null) {
63+
extensionFilters.clear()
64+
extensionFilters.add(value)
65+
}
5266
}
53-
}
54-
67+
private var extensionFilters: MutableList<FileExtensionFilter> = mutableListOf()
68+
set(value) {
69+
if (field.isNotEmpty()) {
70+
value.addAll(field)
71+
}
72+
field = value
73+
}
74+
protected val fxOverwrite = ObservableBoolean("overwrite", false)
5575
abstract val preferences: Preferences
76+
val selectedFileProperty: SimpleObjectProperty<File?> = SimpleObjectProperty<File?>(null)
77+
private val secondaryOptionsSwingNode = SwingNode()
5678

57-
private val myOptionsBuilder: OptionsPageBuilder = OptionsPageBuilder().also {
58-
it.i18N = object : OptionsPageBuilder.I18N() {
59-
protected override fun hasValue(key: String): Boolean {
60-
return if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") true else super.hasValue(
61-
key
62-
)
79+
init {
80+
fxFile.addWatcher { event ->
81+
tryChosenFile(event.newValue)
82+
}
83+
// selectedFileProperty.addListener { _, _, newValue ->
84+
// if (fxFile.value != newValue) {
85+
// fxFile.value = newValue
86+
// }
87+
// }
88+
fxOverwrite.addWatcher { tryChosenFile(fxFile.value) }
89+
}
90+
91+
override val component: Component? = null
92+
override val fxComponent: Node by lazy {
93+
val root = BorderPane()
94+
root.styleClass.add("file-chooser-page")
95+
val sheet = PropertySheetBuilder(RootLocalizer).pane {
96+
file(fxFile) {
97+
editorStyles.add("file-chooser")
98+
this@FileChooserPageBase.extensionFilters = this.extensionFilters
99+
}
100+
if (hasOverwriteOption) {
101+
checkbox(fxOverwrite)
63102
}
103+
}
104+
root.top = sheet.node
64105

65-
protected override fun getValue(key: String): String? {
66-
if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") {
67-
return RootLocalizer.formatText("document.overwrite")
106+
root.center = secondaryOptionsSwingNode
107+
108+
fun showError(msg: String?) {
109+
if (msg != null) {
110+
//UIUtil.setupErrorLabel(myFileLabel, it.newValue)
111+
root.bottom = HBox().apply {
112+
styleClass.add("alert-embedded-box")
113+
children.add(Label(msg).also { it.styleClass.add("alert-error") })
68114
}
69-
return super.getValue(key)
115+
} else {
116+
root.bottom = null
70117
}
71118
}
119+
errorMessage.addWatcher { showError(it.newValue) }
120+
showError(errorMessage.value)
121+
// We need to update the secondary options panel when it's created.
122+
// In FileChooserPageBase, it's updated in setActive(true).
123+
root
124+
}
125+
126+
private fun showError(msg: String?) {
127+
errorMessage.value = msg
128+
}
129+
130+
private val myOptionsBuilder: OptionsPageBuilder = OptionsPageBuilder().also {
131+
// it.i18N = object : OptionsPageBuilder.I18N() {
132+
// protected override fun hasValue(key: String): Boolean {
133+
// return if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") true else super.hasValue(
134+
// key
135+
// )
136+
// }
137+
//
138+
// protected override fun getValue(key: String): String? {
139+
// if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") {
140+
// return RootLocalizer.formatText("document.overwrite")
141+
// }
142+
// return super.getValue(key)
143+
// }
144+
// }
72145
}
73146
private val mySecondaryOptionsComponent = JPanel(BorderLayout()).also {
74147
it.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0))
75148
}
76149
private val myFileLabel = JLabel("")
77-
protected val overwriteOption: BooleanOption = DefaultBooleanOption("overwrite")
78-
79-
val selectedFileProperty: SimpleObjectProperty<File?> = SimpleObjectProperty<File?>(null)
150+
// protected val overwriteOption: BooleanOption = DefaultBooleanOption("overwrite")
151+
//
80152
protected var hasOverwriteOption: Boolean = true
81153
protected var updateChosenFile: (File) -> File = { it }
82154
protected var proposeChosenFile: () -> File = { File(defaultFileName) }
@@ -90,61 +162,67 @@ abstract class FileChooserPageBase protected constructor(
90162
fun tryChosenFile(file: File?) {
91163
myFileLabel.setOpaque(true)
92164
validateFile(file).onSuccess {
93-
selectedFileProperty.set(file)
94-
UIUtil.clearErrorLabel(myFileLabel)
95-
preferences.put(PREF_SELECTED_FILE, chooser.file.absolutePath)
165+
//selectedFileProperty.set(file)
166+
//UIUtil.clearErrorLabel(myFileLabel)
167+
showError(null)
168+
preferences.put(PREF_SELECTED_FILE, fxFile.value?.absolutePath)
96169
}.onFailure {
97-
UIUtil.setupErrorLabel(myFileLabel, it)
170+
showError(it ?: "Something went wrong")
171+
//UIUtil.setupErrorLabel(myFileLabel, it)
98172
}
99173
}
100174

101-
override val component: Component by lazy {
102-
val myComponent = JPanel(BorderLayout())
103-
chooser.setFileSelectionMode(this.fileChooserSelectionMode)
104-
val contentPanel: JComponent = JPanel(BorderLayout())
105-
val fileBox = Box.createVerticalBox()
106-
chooser.setAlignmentX(Component.LEFT_ALIGNMENT)
107-
fileBox.add(this.chooser)
108-
myFileLabel.setAlignmentX(Component.LEFT_ALIGNMENT)
109-
fileBox.add(myFileLabel)
110-
if (hasOverwriteOption) {
111-
fileBox.add(
112-
myOptionsBuilder.createOptionComponent(
113-
GPOptionGroup("exporter", this.overwriteOption), this.overwriteOption
114-
)
115-
)
116-
}
117-
contentPanel.add(fileBox, BorderLayout.NORTH)
118-
contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER)
119-
myComponent.add(contentPanel, BorderLayout.NORTH)
120-
myComponent
121-
}
175+
// override val component: Component by lazy {
176+
// val myComponent = JPanel(BorderLayout())
177+
// chooser.setFileSelectionMode(this.fileChooserSelectionMode)
178+
// val contentPanel: JComponent = JPanel(BorderLayout())
179+
// val fileBox = Box.createVerticalBox()
180+
// chooser.setAlignmentX(Component.LEFT_ALIGNMENT)
181+
// fileBox.add(this.chooser)
182+
// myFileLabel.setAlignmentX(Component.LEFT_ALIGNMENT)
183+
// fileBox.add(myFileLabel)
184+
// if (hasOverwriteOption) {
185+
// fileBox.add(
186+
// myOptionsBuilder.createOptionComponent(
187+
// GPOptionGroup("exporter", this.overwriteOption), this.overwriteOption
188+
// )
189+
// )
190+
// }
191+
// contentPanel.add(fileBox, BorderLayout.NORTH)
192+
// contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER)
193+
// myComponent.add(contentPanel, BorderLayout.NORTH)
194+
// myComponent
195+
// }
122196

123197
protected open fun loadPreferences() {
124198
val oldFile = preferences.get(PREF_SELECTED_FILE, null)
125199
if (oldFile != null) {
126-
chooser.setFile(updateChosenFile(File(oldFile)))
200+
fxFile.value = updateChosenFile(File(oldFile))
127201
} else {
128-
chooser.setFile(proposeChosenFile())
202+
fxFile.value = proposeChosenFile()
129203
}
130204
}
131205

132206
override fun setActive(isActive: Boolean) {
133207
val optionGroups = this.optionGroups
134-
if (isActive == false) {
208+
if (!isActive) {
135209
for (optionGroup in optionGroups) {
136210
optionGroup.commit()
137211
}
138-
if (chooser.file != null) {
139-
preferences.put(PREF_SELECTED_FILE, chooser.file.absolutePath)
212+
fxFile.value?.let {
213+
preferences.put(PREF_SELECTED_FILE, it.absolutePath)
140214
}
141215
} else {
142216
for (optionGroup in optionGroups) {
143217
optionGroup.lock()
144218
}
145219
mySecondaryOptionsComponent.removeAll()
146220
mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH)
147-
chooser.setFileFilter(createFileFilter())
221+
222+
secondaryOptionsSwingNode.content = mySecondaryOptionsComponent
223+
fileFilter = createFileFilter().also {
224+
println("filter = $it")
225+
}
148226
loadPreferences()
149227
}
150228
}
@@ -153,7 +231,7 @@ abstract class FileChooserPageBase protected constructor(
153231
return myOptionsBuilder.buildPlanePage(this.optionGroups.toTypedArray())
154232
}
155233

156-
protected abstract fun createFileFilter(): FileFilter?
234+
protected abstract fun createFileFilter(): FileExtensionFilter?
157235

158236
protected abstract val optionGroups: List<GPOptionGroup>
159237

0 commit comments

Comments
 (0)