From 8d2a753666923ed9020b470dbae8dfa676d97f21 Mon Sep 17 00:00:00 2001 From: Dmitry Barashev Date: Sun, 8 Mar 2026 17:07:22 +0400 Subject: [PATCH 1/2] Rewriting the file chooser page in the import and export wizards in JavaFX --- .../biz/ganttproject/app/PropertySheet.kt | 8 +- .../export/ExportFileChooserPage.kt | 24 +- .../ganttproject/gui/FileChooserPageBase.kt | 190 ++++++--- .../ganttproject/gui/GanttDialogPerson.form | 378 ------------------ .../gui/projectwizard/WizardImplFx.kt | 9 +- .../ganttproject/importer/ImportFileWizard.kt | 27 +- .../sass/biz/ganttproject/app/errors.scss | 2 +- .../sass/biz/ganttproject/impex/Exporter.scss | 15 + 8 files changed, 192 insertions(+), 461 deletions(-) delete mode 100644 ganttproject/src/main/java/net/sourceforge/ganttproject/gui/GanttDialogPerson.form diff --git a/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt b/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt index a105989f21..f937aa12ee 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt @@ -295,7 +295,7 @@ class PropertyPaneBuilderImpl(private val localizer: Localizer, private val grid } val resultFile = fileChooser.showOpenDialog(null) - option.value = resultFile + option.set(resultFile, textField) resultFile?.let { textField.text = it.absolutePath } @@ -306,8 +306,14 @@ class PropertyPaneBuilderImpl(private val localizer: Localizer, private val grid onClick = { onBrowse() }, styleClass = "btn" ) + textField.text = option.value?.absolutePath ?: "" textField.id = option.id displayOptions?.editorStyles?.let(textField.styleClass::addAll) + option.addWatcher { + if (it.trigger != textField) { + textField.text = option.value?.absolutePath ?: "" + } + } return textField // return HBox().apply { // HBox.setHgrow(textField, Priority.ALWAYS) diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt index 75a126d543..549bc3fcb4 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt @@ -19,7 +19,9 @@ package net.sourceforge.ganttproject.export import biz.ganttproject.app.RootLocalizer +import biz.ganttproject.core.option.FileExtensionFilter import biz.ganttproject.core.option.GPOptionGroup +import biz.ganttproject.core.option.ObservableString import biz.ganttproject.storage.asLocalDocument import biz.ganttproject.storage.getDefaultLocalFolder import com.github.michaelbull.result.Err @@ -49,7 +51,8 @@ internal class ExportFileChooserPage( myProject.document, uiFacade, fileChooserTitle = i18n.formatText("selectFileToExport"), fileChooserSelectionMode = JFileChooser.FILES_AND_DIRECTORIES, - pageTitle = i18n.formatText("selectFileToExport") + pageTitle = i18n.formatText("selectFileToExport"), + errorMessage = ObservableString("", "") ) { private val myWebPublishingGroup: GPOptionGroup = GPOptionGroup( "exporter.webPublishing", myState.publishInWebOption @@ -60,10 +63,12 @@ internal class ExportFileChooserPage( proposeChosenFile = { proposeOutputFile(myProject, myState.exporter) ?: File(defaultFileName) } - overwriteOption.addChangeValueListener { tryChosenFile(chooser.file) } - selectedFileProperty.addListener { _, _, newValue -> - myState.file = newValue + fxFile.addWatcher { + myState.file = it.newValue } +// selectedFileProperty.addListener { _, _, newValue -> +// myState.file = newValue +// } } override fun validateFile(file: File?): Result { @@ -71,7 +76,7 @@ internal class ExportFileChooserPage( return Err("File cannot be null") } - overwriteOption.getIsWritableProperty().set(false, this) + fxOverwrite.isWritable.value = false if (!file.exists()) { val parent = file.getParentFile() if (!parent.exists()) { @@ -101,8 +106,8 @@ internal class ExportFileChooserPage( ) } } else { - overwriteOption.getIsWritableProperty().set(true, this) - if (!overwriteOption.isChecked()) { + fxOverwrite.isWritable.value = true + if (!fxOverwrite.value) { return Err(i18n.formatText("fileChooser.warning.fileExists")) } } @@ -113,10 +118,7 @@ internal class ExportFileChooserPage( return myState.exporter.getCustomOptionsUI() ?: super.createSecondaryOptionsPanel() } - override fun createFileFilter(): FileFilter = - ExtensionBasedFileFilter( - myState.exporter.getFileNamePattern(), myState.exporter.getFileTypeDescription() - ) + override fun createFileFilter(): FileExtensionFilter = FileExtensionFilter(myState.exporter.getFileTypeDescription(), listOf(myState.exporter.getFileNamePattern())) override val optionGroups: List get() = listOf(myWebPublishingGroup) + (myState.exporter?.secondaryOptions ?: emptyList()) diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt index 626c37a763..b5de5b5a5c 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt @@ -18,14 +18,20 @@ */ package net.sourceforge.ganttproject.gui +//import biz.ganttproject.platform.getMeaningfulMessage +//import biz.ganttproject.platform.getMeaningfulMessage +import biz.ganttproject.app.PropertySheetBuilder import biz.ganttproject.app.RootLocalizer -import biz.ganttproject.core.option.BooleanOption -import biz.ganttproject.core.option.DefaultBooleanOption -import biz.ganttproject.core.option.GPOptionGroup +import biz.ganttproject.core.option.* import com.github.michaelbull.result.Result import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onSuccess import javafx.beans.property.SimpleObjectProperty +import javafx.embed.swing.SwingNode +import javafx.scene.Node +import javafx.scene.control.Label +import javafx.scene.layout.BorderPane +import javafx.scene.layout.HBox import net.sourceforge.ganttproject.document.Document import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder import net.sourceforge.ganttproject.gui.projectwizard.WizardPage @@ -33,8 +39,10 @@ import org.osgi.service.prefs.Preferences import java.awt.BorderLayout import java.awt.Component import java.io.File -import javax.swing.* -import javax.swing.filechooser.FileFilter +import javax.swing.BorderFactory +import javax.swing.JFileChooser +import javax.swing.JLabel +import javax.swing.JPanel /** * Base class for the file chooser pages in the Import and Export wizards. @@ -44,39 +52,103 @@ abstract class FileChooserPageBase protected constructor( uiFacade: UIFacade?, val fileChooserTitle: String?, val pageTitle: String?, - val fileChooserSelectionMode: Int = JFileChooser.FILES_ONLY + val fileChooserSelectionMode: Int = JFileChooser.FILES_ONLY, + val errorMessage: ObservableString ) : WizardPage { - protected val chooser: TextFieldAndFileChooserComponent = object : TextFieldAndFileChooserComponent(uiFacade, this.fileChooserTitle) { - override fun onFileChosen(file: File?) { - tryChosenFile(file) + val fxFile = ObservableFile("file", null) + private var fileFilter: FileExtensionFilter? = null + set(value) { + field = value + if (value != null) { + extensionFilters.clear() + extensionFilters.add(value) + } } - } - + private var extensionFilters: MutableList = mutableListOf() + set(value) { + if (field.isNotEmpty()) { + value.addAll(field) + } + field = value + } + protected val fxOverwrite = ObservableBoolean("overwrite", false) abstract val preferences: Preferences + val selectedFileProperty: SimpleObjectProperty = SimpleObjectProperty(null) + private val secondaryOptionsSwingNode = SwingNode() - private val myOptionsBuilder: OptionsPageBuilder = OptionsPageBuilder().also { - it.i18N = object : OptionsPageBuilder.I18N() { - protected override fun hasValue(key: String): Boolean { - return if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") true else super.hasValue( - key - ) + init { + fxFile.addWatcher { event -> + tryChosenFile(event.newValue) + } +// selectedFileProperty.addListener { _, _, newValue -> +// if (fxFile.value != newValue) { +// fxFile.value = newValue +// } +// } + fxOverwrite.addWatcher { tryChosenFile(fxFile.value) } + } + + override val component: Component? = null + override val fxComponent: Node by lazy { + val root = BorderPane() + root.styleClass.add("file-chooser-page") + val sheet = PropertySheetBuilder(RootLocalizer).pane { + file(fxFile) { + editorStyles.add("file-chooser") + this@FileChooserPageBase.extensionFilters = this.extensionFilters + } + if (hasOverwriteOption) { + checkbox(fxOverwrite) } + } + root.top = sheet.node - protected override fun getValue(key: String): String? { - if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") { - return RootLocalizer.formatText("document.overwrite") + root.center = secondaryOptionsSwingNode + + fun showError(msg: String?) { + if (msg != null) { + //UIUtil.setupErrorLabel(myFileLabel, it.newValue) + root.bottom = HBox().apply { + styleClass.add("alert-embedded-box") + children.add(Label(msg).also { it.styleClass.add("alert-error") }) } - return super.getValue(key) + } else { + root.bottom = null } } + errorMessage.addWatcher { showError(it.newValue) } + showError(errorMessage.value) + // We need to update the secondary options panel when it's created. + // In FileChooserPageBase, it's updated in setActive(true). + root + } + + private fun showError(msg: String?) { + errorMessage.value = msg + } + + private val myOptionsBuilder: OptionsPageBuilder = OptionsPageBuilder().also { +// it.i18N = object : OptionsPageBuilder.I18N() { +// protected override fun hasValue(key: String): Boolean { +// return if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") true else super.hasValue( +// key +// ) +// } +// +// protected override fun getValue(key: String): String? { +// if (key == getCanonicalOptionLabelKey(overwriteOption) + ".trailing") { +// return RootLocalizer.formatText("document.overwrite") +// } +// return super.getValue(key) +// } +// } } private val mySecondaryOptionsComponent = JPanel(BorderLayout()).also { it.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)) } private val myFileLabel = JLabel("") - protected val overwriteOption: BooleanOption = DefaultBooleanOption("overwrite") - - val selectedFileProperty: SimpleObjectProperty = SimpleObjectProperty(null) +// protected val overwriteOption: BooleanOption = DefaultBooleanOption("overwrite") +// protected var hasOverwriteOption: Boolean = true protected var updateChosenFile: (File) -> File = { it } protected var proposeChosenFile: () -> File = { File(defaultFileName) } @@ -90,53 +162,55 @@ abstract class FileChooserPageBase protected constructor( fun tryChosenFile(file: File?) { myFileLabel.setOpaque(true) validateFile(file).onSuccess { - selectedFileProperty.set(file) - UIUtil.clearErrorLabel(myFileLabel) - preferences.put(PREF_SELECTED_FILE, chooser.file.absolutePath) + //selectedFileProperty.set(file) + //UIUtil.clearErrorLabel(myFileLabel) + showError(null) + preferences.put(PREF_SELECTED_FILE, fxFile.value?.absolutePath) }.onFailure { - UIUtil.setupErrorLabel(myFileLabel, it) + showError(it ?: "Something went wrong") + //UIUtil.setupErrorLabel(myFileLabel, it) } } - override val component: Component by lazy { - val myComponent = JPanel(BorderLayout()) - chooser.setFileSelectionMode(this.fileChooserSelectionMode) - val contentPanel: JComponent = JPanel(BorderLayout()) - val fileBox = Box.createVerticalBox() - chooser.setAlignmentX(Component.LEFT_ALIGNMENT) - fileBox.add(this.chooser) - myFileLabel.setAlignmentX(Component.LEFT_ALIGNMENT) - fileBox.add(myFileLabel) - if (hasOverwriteOption) { - fileBox.add( - myOptionsBuilder.createOptionComponent( - GPOptionGroup("exporter", this.overwriteOption), this.overwriteOption - ) - ) - } - contentPanel.add(fileBox, BorderLayout.NORTH) - contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER) - myComponent.add(contentPanel, BorderLayout.NORTH) - myComponent - } +// override val component: Component by lazy { +// val myComponent = JPanel(BorderLayout()) +// chooser.setFileSelectionMode(this.fileChooserSelectionMode) +// val contentPanel: JComponent = JPanel(BorderLayout()) +// val fileBox = Box.createVerticalBox() +// chooser.setAlignmentX(Component.LEFT_ALIGNMENT) +// fileBox.add(this.chooser) +// myFileLabel.setAlignmentX(Component.LEFT_ALIGNMENT) +// fileBox.add(myFileLabel) +// if (hasOverwriteOption) { +// fileBox.add( +// myOptionsBuilder.createOptionComponent( +// GPOptionGroup("exporter", this.overwriteOption), this.overwriteOption +// ) +// ) +// } +// contentPanel.add(fileBox, BorderLayout.NORTH) +// contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER) +// myComponent.add(contentPanel, BorderLayout.NORTH) +// myComponent +// } protected open fun loadPreferences() { val oldFile = preferences.get(PREF_SELECTED_FILE, null) if (oldFile != null) { - chooser.setFile(updateChosenFile(File(oldFile))) + fxFile.value = updateChosenFile(File(oldFile)) } else { - chooser.setFile(proposeChosenFile()) + fxFile.value = proposeChosenFile() } } override fun setActive(isActive: Boolean) { val optionGroups = this.optionGroups - if (isActive == false) { + if (!isActive) { for (optionGroup in optionGroups) { optionGroup.commit() } - if (chooser.file != null) { - preferences.put(PREF_SELECTED_FILE, chooser.file.absolutePath) + fxFile.value?.let { + preferences.put(PREF_SELECTED_FILE, it.absolutePath) } } else { for (optionGroup in optionGroups) { @@ -144,7 +218,11 @@ abstract class FileChooserPageBase protected constructor( } mySecondaryOptionsComponent.removeAll() mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH) - chooser.setFileFilter(createFileFilter()) + + secondaryOptionsSwingNode.content = mySecondaryOptionsComponent + fileFilter = createFileFilter().also { + println("filter = $it") + } loadPreferences() } } @@ -153,7 +231,7 @@ abstract class FileChooserPageBase protected constructor( return myOptionsBuilder.buildPlanePage(this.optionGroups.toTypedArray()) } - protected abstract fun createFileFilter(): FileFilter? + protected abstract fun createFileFilter(): FileExtensionFilter? protected abstract val optionGroups: List diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/GanttDialogPerson.form b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/GanttDialogPerson.form deleted file mode 100644 index 43f6bbde2f..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/GanttDialogPerson.form +++ /dev/null @@ -1,378 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt index 514cd59073..2eba2b9699 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt @@ -24,11 +24,13 @@ import biz.ganttproject.app.RootLocalizer import biz.ganttproject.app.dialog import biz.ganttproject.app.setSwingBackground import biz.ganttproject.core.option.ObservableBoolean +import biz.ganttproject.core.option.ObservableString import biz.ganttproject.lib.fx.vbox import javafx.beans.property.SimpleIntegerProperty import javafx.embed.swing.SwingNode import javafx.event.ActionEvent import javafx.scene.control.Button +import javafx.scene.control.Label import javafx.scene.layout.StackPane import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -51,13 +53,16 @@ open class WizardModel { var title: String = "" var dialogId: String = "" var onOk: () -> Unit = {} - var canFinish: () -> Boolean = { true } + var canFinish: () -> Boolean = { errorMessage.value.isNullOrBlank() } var hasNext: () -> Boolean = { currentPage < pages.size - 1 } var currentPage = 0 val pages = mutableListOf() val needsRefresh = ObservableBoolean("", false) val pageCountProperty = SimpleIntegerProperty(0) + val errorMessage = ObservableString("", "").also { + it.addWatcher { needsRefresh.set(true, this) } + } fun addPage(page: WizardPage) { pages.add(page) @@ -72,6 +77,8 @@ open class WizardModel { fun hasPrev(): Boolean { return currentPage > 0 } + + } /** diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt index 97580706c4..2f1fbf9fe2 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt @@ -19,6 +19,7 @@ package net.sourceforge.ganttproject.importer import biz.ganttproject.app.* +import biz.ganttproject.core.option.FileExtensionFilter import biz.ganttproject.core.option.GPOptionGroup import com.github.michaelbull.result.Err import com.github.michaelbull.result.Ok @@ -27,7 +28,6 @@ import com.github.michaelbull.result.andThen import javafx.beans.property.SimpleObjectProperty import javafx.scene.Node import net.sourceforge.ganttproject.IGanttProject -import net.sourceforge.ganttproject.filter.ExtensionBasedFileFilter import net.sourceforge.ganttproject.gui.FileChooserPageBase import net.sourceforge.ganttproject.gui.UIFacade import net.sourceforge.ganttproject.gui.projectwizard.WizardModel @@ -37,7 +37,6 @@ import net.sourceforge.ganttproject.plugins.PluginManager.getExtensions import org.osgi.service.prefs.Preferences import java.awt.Component import java.io.File -import javax.swing.filechooser.FileFilter /** * Wizard for importing files into a Gantt project. @@ -48,7 +47,7 @@ class ImportFileWizard(uiFacade: UIFacade, project: IGanttProject, pluginPrefere init { importers.forEach { it.setContext(project, uiFacade, pluginPreferences) } val filePage = ImportFileChooserPage(wizardModel, project, pluginPreferences, uiFacade) - filePage.selectedFileProperty.addListener { _, _, _ -> + filePage.fxFile.addWatcher { wizardModel.needsRefresh.set(true, this) } wizardModel.importer = importers.firstOrNull() @@ -95,7 +94,9 @@ class ImporterWizardModel: WizardModel() { val customPageProperty = SimpleObjectProperty(null) init { - canFinish = { importer != null && file != null } + canFinish = { + importer != null && file != null && errorMessage.value.isNullOrBlank() + } hasNext = { when (currentPage) { 0 -> importer != null 1 -> customPageProperty.get() != null && file != null @@ -148,31 +149,31 @@ private class ImporterChooserPageFx(importers: List, model: ImporterWi * Wizard page for choosing a file to import from. */ private class ImportFileChooserPage( - private val state: ImporterWizardModel, project: IGanttProject, private val prefs: Preferences, uiFacade: UIFacade) + private val model: ImporterWizardModel, project: IGanttProject, private val prefs: Preferences, uiFacade: UIFacade) : FileChooserPageBase(project.document, uiFacade, fileChooserTitle = "", - pageTitle = i18n.formatText("importerFileChooserPageTitle")) { + pageTitle = i18n.formatText("importerFileChooserPageTitle"), errorMessage = model.errorMessage) { - val importer get() = state.importer + val importer get() = model.importer - override val preferences: Preferences get() = prefs.node(state.importer?.id ?: "") + override val preferences: Preferences get() = prefs.node(model.importer?.id ?: "") init { hasOverwriteOption = false - selectedFileProperty.addListener { _, _, newValue -> - state.file = newValue + fxFile.addWatcher { + model.file = it.newValue } } - override fun createFileFilter(): FileFilter? = + override fun createFileFilter(): FileExtensionFilter? = importer?.let { - return ExtensionBasedFileFilter(it.getFileNamePattern(), it.getFileTypeDescription()) + FileExtensionFilter(it.getFileTypeDescription(), it.getFileNamePattern().split("|").map { "*.$it" }) } override val optionGroups: List = emptyList() override fun validateFile(file: File?): Result { return super.validateFile(file).andThen { file -> - if (file?.isDirectory ?: false) { + if (file?.isDirectory == true) { Err("It is a directory") } else { Ok(file) diff --git a/ganttproject/src/main/sass/biz/ganttproject/app/errors.scss b/ganttproject/src/main/sass/biz/ganttproject/app/errors.scss index e2fa4cb94f..65862d240a 100644 --- a/ganttproject/src/main/sass/biz/ganttproject/app/errors.scss +++ b/ganttproject/src/main/sass/biz/ganttproject/app/errors.scss @@ -20,7 +20,7 @@ @import "theme"; @mixin error-label() { - -fx-text-fill: $gp-error; + -fx-text-fill: $gp-medium-gray; -fx-font-weight: bold; -fx-font-size: normal; } diff --git a/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss b/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss index 981c39bdc3..2eb555fe1f 100644 --- a/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss +++ b/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss @@ -16,9 +16,11 @@ * You should have received a copy of the GNU General Public License * along with GanttProject. If not, see . */ +@import "../app/textfields"; @import "../app/theme"; @import "../app/dialogs"; @import "../app/buttons"; +@import "../app/errors"; @import "../app/validation"; .dlg-export-progress { @@ -81,5 +83,18 @@ } } } + + .file-chooser-page { + .file-chooser { + @include textfield-with-button($gp-light-gray, $gp-dark-gray); + } + + .alert-embedded-box { + @include error-embedded-box($gp-error); + .alert-error { + @include error-label(); + } + } + } } From 17ef5b72779c603a8644103d5a2f8c2b37fea355 Mon Sep 17 00:00:00 2001 From: Dmitry Barashev Date: Wed, 11 Mar 2026 06:27:40 +0400 Subject: [PATCH 2/2] - Improved file option editor component (mostly i18n) - Improved the process of updating wizard buttons - Updated the file chooser page in the export wizard --- .../core/option/PropertyPaneBuilder.kt | 9 +- .../main/java/biz/ganttproject/app/Dialog.kt | 3 + .../biz/ganttproject/app/PropertySheet.kt | 55 +--------- .../ganttproject/app/PropertySheetEditors.kt | 81 +++++++++++++- .../export/ExportFileChooserPage.kt | 30 +++--- .../export/ExportFileWizardImpl.java | 81 +++----------- .../export/ExportFileWizardImpl.kt | 34 ++++++ .../export/ExporterChooserPage.java | 100 ------------------ .../export/ExporterChooserPageFx.kt | 5 +- .../ganttproject/gui/FileChooserPageBase.kt | 74 +++++-------- .../gui/projectwizard/WizardImplFx.kt | 14 ++- .../ganttproject/importer/ImportFileWizard.kt | 17 +-- 12 files changed, 196 insertions(+), 307 deletions(-) create mode 100644 ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.kt delete mode 100644 ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPage.java diff --git a/biz.ganttproject.core/src/main/java/biz/ganttproject/core/option/PropertyPaneBuilder.kt b/biz.ganttproject.core/src/main/java/biz/ganttproject/core/option/PropertyPaneBuilder.kt index 7fb8dbffe0..c23829a79b 100644 --- a/biz.ganttproject.core/src/main/java/biz/ganttproject/core/option/PropertyPaneBuilder.kt +++ b/biz.ganttproject.core/src/main/java/biz/ganttproject/core/option/PropertyPaneBuilder.kt @@ -67,8 +67,13 @@ data class FileExtensionFilter(val description: String, val extensions: List = mutableListOf()) : - PropertyDisplayOptions() +data class FileDisplayOptions( + var isSaveNotOpen: Boolean = false, + var allowMultipleSelection: Boolean = false, + var browseButtonText: String = "Browse...", + var chooserTitle: String = "Choose a file", + val extensionFilters: MutableList = mutableListOf()) + : PropertyDisplayOptions() /** * Options for displaying integer fields in a property pane. diff --git a/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt b/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt index f69e3e6f68..aabc633f6f 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt @@ -19,6 +19,7 @@ along with GanttProject. If not, see . package biz.ganttproject.app import biz.ganttproject.FXUtil +import biz.ganttproject.app.DialogPlacement.applicationWindow import biz.ganttproject.centerOnOwner import biz.ganttproject.colorFromUiManager import biz.ganttproject.lib.fx.VBoxBuilder @@ -169,6 +170,8 @@ class DialogPaneExt : DialogPane() { } private val dialogStack = mutableListOf>() +fun topWindow(): Window? = dialogStack.lastOrNull()?.dialogPane?.scene?.window ?: applicationWindow + fun dialog(title: String? = null, id: String? = null, contentBuilder: (DialogController) -> Unit) { Platform.runLater { try { diff --git a/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt b/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt index f937aa12ee..1ddfe36e2d 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/PropertySheet.kt @@ -100,7 +100,7 @@ class PropertyPaneBuilderImpl(private val localizer: Localizer, private val grid fun file(property: ObservableFile, optionValues: (FileDisplayOptions.()->Unit)? = null) { rowBuilders.add(run { - val options = optionValues?.let { FileDisplayOptions().apply(it) } + val options = optionValues?.let { FileDisplayOptions().apply(it) } ?: FileDisplayOptions() createOptionItem(property, createFileOptionEditor(property, options)) }) } @@ -272,58 +272,9 @@ class PropertyPaneBuilderImpl(private val localizer: Localizer, private val grid return textField } - private fun createFileOptionEditor(option: ObservableFile, displayOptions: FileDisplayOptions? = null): Node { - val textField = CustomTextField() - val onBrowse = { - val fileChooser = FileChooser(); - var initialFile: File? = File(textField.text) - while (initialFile?.exists() == false) { - initialFile = initialFile.parentFile - } - initialFile?.let { - if (it.isDirectory) { - fileChooser.initialDirectory = it - } else { - fileChooser.initialDirectory = it.parentFile - } - } - fileChooser.title = "Choose a file" - displayOptions?.let { - it.extensionFilters.forEach {filter -> - fileChooser.extensionFilters.add(FileChooser.ExtensionFilter(filter.description, filter.extensions)) - } - } - - val resultFile = fileChooser.showOpenDialog(null) - option.set(resultFile, textField) - resultFile?.let { - textField.text = it.absolutePath - } - } - textField.right = buildFontAwesomeButton( - iconName = FontAwesomeIcon.SEARCH.name, - label = "Browse...", - onClick = { onBrowse() }, - styleClass = "btn" - ) - textField.text = option.value?.absolutePath ?: "" - textField.id = option.id - displayOptions?.editorStyles?.let(textField.styleClass::addAll) - option.addWatcher { - if (it.trigger != textField) { - textField.text = option.value?.absolutePath ?: "" - } - } - return textField -// return HBox().apply { -// HBox.setHgrow(textField, Priority.ALWAYS) -// children.add(textField) -// children.add(Region().also { -// it.padding = Insets(0.0, 5.0, 0.0, 0.0) -// }) -// children.add(btn) -// } + private fun createFileOptionEditor(option: ObservableFile, displayOptions: FileDisplayOptions = FileDisplayOptions()): Node { + return FileOptionEditor(option, displayOptions).node } fun createDateOptionEditor(option: ObservableDate, displayOptions: DateDisplayOptions = DateDisplayOptions(createDateConverter())): DatePicker { diff --git a/ganttproject/src/main/java/biz/ganttproject/app/PropertySheetEditors.kt b/ganttproject/src/main/java/biz/ganttproject/app/PropertySheetEditors.kt index b765ec91fa..ff29592495 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/PropertySheetEditors.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/PropertySheetEditors.kt @@ -18,17 +18,21 @@ along with GanttProject. If not, see . */ package biz.ganttproject.app -import biz.ganttproject.core.option.DropdownDisplayOptions -import biz.ganttproject.core.option.ObservableChoice -import biz.ganttproject.core.option.ObservableProperty +import biz.ganttproject.core.option.* +import biz.ganttproject.lib.fx.buildFontAwesomeButton +import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon import javafx.collections.FXCollections import javafx.event.EventHandler import javafx.scene.Node import javafx.scene.control.ComboBox import javafx.scene.control.ListCell import javafx.scene.layout.HBox +import javafx.stage.FileChooser import javafx.util.Callback import javafx.util.StringConverter +import org.controlsfx.control.textfield.CustomTextField +import java.io.File +import java.util.* /** * Creates a dropdown editor for the given choice option. @@ -72,3 +76,74 @@ fun createDropdownEditor(option: ObservableProperty, key2i18n: List + fileChooser.extensionFilters.add(FileChooser.ExtensionFilter(filter.description, filter.extensions)) + } + } + + val ownerWindow = topWindow() + val resultFile = + if (displayOptions.isSaveNotOpen) fileChooser.showSaveDialog(ownerWindow) + else fileChooser.showOpenDialog(ownerWindow) + resultFile?.let { + option.set(resultFile, textField) + textField.text = it.absolutePath + } + } + + private fun onTextChange() { + if (myTimerTask == null) { + myTimerTask = object : TimerTask() { + override fun run() { + val file = File(textField.text) + option.set(file, textField) + myTimerTask = null + } + } + ourTimer.schedule(myTimerTask, 1000) + } + } +} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt index 549bc3fcb4..5a7fd72100 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt @@ -21,54 +21,50 @@ package net.sourceforge.ganttproject.export import biz.ganttproject.app.RootLocalizer import biz.ganttproject.core.option.FileExtensionFilter import biz.ganttproject.core.option.GPOptionGroup -import biz.ganttproject.core.option.ObservableString import biz.ganttproject.storage.asLocalDocument import biz.ganttproject.storage.getDefaultLocalFolder import com.github.michaelbull.result.Err import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result import net.sourceforge.ganttproject.IGanttProject -import net.sourceforge.ganttproject.filter.ExtensionBasedFileFilter import net.sourceforge.ganttproject.gui.FileChooserPageBase -import net.sourceforge.ganttproject.gui.UIFacade import net.sourceforge.ganttproject.gui.UIUtil import net.sourceforge.ganttproject.util.FileUtil.replaceExtension import org.osgi.service.prefs.Preferences import java.awt.Component import java.io.File import javax.swing.JFileChooser -import javax.swing.filechooser.FileFilter /** * A wizard page for choosing a file to export to. */ internal class ExportFileChooserPage( - private val myState: ExportFileWizardImpl.State, + private val myState: ExportWizardModel, private val myProject: IGanttProject, - override val preferences: Preferences, - uiFacade: UIFacade? + override val preferences: Preferences ) : FileChooserPageBase( - myProject.document, uiFacade, + myProject.document, fileChooserTitle = i18n.formatText("selectFileToExport"), fileChooserSelectionMode = JFileChooser.FILES_AND_DIRECTORIES, pageTitle = i18n.formatText("selectFileToExport"), - errorMessage = ObservableString("", "") + errorMessage = myState.errorMessage ) { private val myWebPublishingGroup: GPOptionGroup = GPOptionGroup( "exporter.webPublishing", myState.publishInWebOption ).also { it.isTitled = false } init { - updateChosenFile = { replaceExtension(it, myState.exporter.proposeFileExtension()) } + updateChosenFile = { file -> + myState.exporter?.let { + replaceExtension(file, it.proposeFileExtension()) + } ?: file + } proposeChosenFile = { - proposeOutputFile(myProject, myState.exporter) ?: File(defaultFileName) + myState.exporter?.let {proposeOutputFile(myProject, it) } ?: File(defaultFileName) } fxFile.addWatcher { myState.file = it.newValue } -// selectedFileProperty.addListener { _, _, newValue -> -// myState.file = newValue -// } } override fun validateFile(file: File?): Result { @@ -115,10 +111,12 @@ internal class ExportFileChooserPage( } override fun createSecondaryOptionsPanel(): Component { - return myState.exporter.getCustomOptionsUI() ?: super.createSecondaryOptionsPanel() + return myState.exporter?.getCustomOptionsUI() ?: super.createSecondaryOptionsPanel() } - override fun createFileFilter(): FileExtensionFilter = FileExtensionFilter(myState.exporter.getFileTypeDescription(), listOf(myState.exporter.getFileNamePattern())) + override fun createFileFilter(): FileExtensionFilter? = myState.exporter?.let { + FileExtensionFilter(it.getFileTypeDescription(), listOf(it.getFileNamePattern())) + } override val optionGroups: List get() = listOf(myWebPublishingGroup) + (myState.exporter?.secondaryOptions ?: emptyList()) diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.java index e1e37cf9fb..ad3d6833ca 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.java @@ -19,27 +19,25 @@ of the License, or (at your option) any later version. package net.sourceforge.ganttproject.export; import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; import java.util.List; import javax.swing.SwingUtilities; import biz.ganttproject.app.InternationalizationCoreKt; import kotlin.Unit; -import net.sourceforge.ganttproject.gui.projectwizard.WizardModel; import net.sourceforge.ganttproject.gui.projectwizard.WizardImplFxKt; import org.osgi.service.prefs.Preferences; -import biz.ganttproject.core.option.BooleanOption; import biz.ganttproject.core.option.ChangeValueEvent; import biz.ganttproject.core.option.ChangeValueListener; -import biz.ganttproject.core.option.DefaultBooleanOption; import net.sourceforge.ganttproject.GPLogger; import net.sourceforge.ganttproject.IGanttProject; import net.sourceforge.ganttproject.gui.UIFacade; import net.sourceforge.ganttproject.plugins.PluginManager; +import static net.sourceforge.ganttproject.export.ExportFileWizardImplKt.getOurLastSelectedExporter; + + /** * @author bard */ @@ -47,11 +45,9 @@ public class ExportFileWizardImpl { private final IGanttProject myProject; - private final State myState; - private static Exporter ourLastSelectedExporter; private static List ourExporters; - private final WizardModel wizardModel = new WizardModel(); + private final ExportWizardModel wizardModel = new ExportWizardModel(); public ExportFileWizardImpl(UIFacade uiFacade, IGanttProject project, Preferences pluginPreferences) { wizardModel.setTitle(InternationalizationCoreKt.getRootLocalizer().formatText("exportWizard.dialog.title")); @@ -59,46 +55,35 @@ public ExportFileWizardImpl(UIFacade uiFacade, IGanttProject project, Preference final Preferences exportNode = pluginPreferences.node("/instance/net.sourceforge.ganttproject/export"); myProject = project; - myState = new State(); - myState.myPublishInWebOption.setValue(exportNode.getBoolean("publishInWeb", false)); - myState.myPublishInWebOption.addChangeValueListener(new ChangeValueListener() { + wizardModel.getPublishInWebOption().setValue(exportNode.getBoolean("publishInWeb", false)); + wizardModel.getPublishInWebOption().addChangeValueListener(new ChangeValueListener() { @Override public void changeValue(ChangeValueEvent event) { - exportNode.putBoolean("publishInWeb", myState.myPublishInWebOption.getValue()); + exportNode.putBoolean("publishInWeb", wizardModel.getPublishInWebOption().getValue()); } }); if (ourExporters == null) { ourExporters = PluginManager.getExporters(); } - myState.setExporter(ourLastSelectedExporter == null ? ourExporters.get(0) : ourLastSelectedExporter); + wizardModel.setExporter(getOurLastSelectedExporter() == null ? ourExporters.get(0) : getOurLastSelectedExporter()); for (Exporter e : ourExporters) { e.setContext(project, uiFacade, pluginPreferences); } - var fileChooserPage = new ExportFileChooserPage(myState, myProject, exportNode, uiFacade); - fileChooserPage.getSelectedFileProperty().addListener( (observable, oldValue, newValue) -> { - wizardModel.getNeedsRefresh().set(true, this); - }); - wizardModel.addPage(new ExporterChooserPageFx(ourExporters, myState)); + var fileChooserPage = new ExportFileChooserPage(wizardModel, myProject, exportNode); + wizardModel.addPage(new ExporterChooserPageFx(ourExporters, wizardModel)); wizardModel.addPage(fileChooserPage); wizardModel.setOnOk(() -> { onOkPressed(); return Unit.INSTANCE; }); - wizardModel.setCanFinish(this::canFinish); - } - - protected boolean canFinish() { - return myState.getExporter() != null && myState.myUrl != null && "file".equals(myState.getUrl().getProtocol()); } protected void onOkPressed() { SwingUtilities.invokeLater(() -> { try { ExportFinalizationJob finalizationJob = new ExportFinalizationJobImpl(); - if ("file".equals(myState.getUrl().getProtocol())) { - myState.getExporter().run(new File(myState.getUrl().toURI()), finalizationJob); - } + wizardModel.getExporter().run(wizardModel.getFile(), finalizationJob); } catch (Exception e) { GPLogger.log(e); } @@ -112,53 +97,11 @@ public void show() { private class ExportFinalizationJobImpl implements ExportFinalizationJob { @Override public void run(File[] exportedFiles) { - if (myState.getPublishInWebOption().isChecked() && exportedFiles.length > 0) { + if (wizardModel.getPublishInWebOption().isChecked() && exportedFiles.length > 0) { WebPublisher publisher = new WebPublisher(); publisher.run(exportedFiles, myProject.getDocumentManager().getFTPOptions()); } } } - public static class State { - private Exporter myExporter; - - private final BooleanOption myPublishInWebOption = new DefaultBooleanOption("exporter.publishInWeb"); - - private URL myUrl; - private File myFile; - - void setExporter(Exporter exporter) { - myExporter = exporter; - ExportFileWizardImpl.ourLastSelectedExporter = exporter; - } - - Exporter getExporter() { - return myExporter; - } - - BooleanOption getPublishInWebOption() { - return myPublishInWebOption; - } - - public URL getUrl() { - return myUrl; - } - - public void setFile(File file) { - myFile = file; - if (file != null) { - try { - myUrl = file.toURI().toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } else { - myUrl = null; - } - } - - public File getFile() { - return myFile; - } - } } \ No newline at end of file diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.kt new file mode 100644 index 0000000000..1460219095 --- /dev/null +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.kt @@ -0,0 +1,34 @@ +package net.sourceforge.ganttproject.export + +import biz.ganttproject.core.option.BooleanOption +import biz.ganttproject.core.option.DefaultBooleanOption +import net.sourceforge.ganttproject.gui.projectwizard.WizardModel +import java.io.File + +var ourLastSelectedExporter: Exporter? = null + +class ExportWizardModel : WizardModel() { + private var myExporter: Exporter? = null + + val publishInWebOption: BooleanOption = DefaultBooleanOption("exporter.publishInWeb") + + var exporter: Exporter? + get() = myExporter + set(exporter) { + myExporter = exporter + ourLastSelectedExporter = exporter + } + + var file: File? = null + set(value) { + field = value + needsRefresh.set(true, this) + } + + init { + canFinish = { + exporter != null && file != null && errorMessage.value.isNullOrBlank() + } + } + +} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPage.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPage.java deleted file mode 100644 index 89a8b33cc8..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPage.java +++ /dev/null @@ -1,100 +0,0 @@ -/* -Copyright 2005-2012 GanttProject Team - -This file is part of GanttProject, an opensource project management tool. - -GanttProject is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - -GanttProject is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with GanttProject. If not, see . - */ -package net.sourceforge.ganttproject.export; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.util.List; - -import javax.swing.AbstractAction; -import javax.swing.Action; - -import biz.ganttproject.core.option.GPOptionGroup; - -import net.sourceforge.ganttproject.export.ExportFileWizardImpl.State; -import net.sourceforge.ganttproject.gui.options.GPOptionChoicePanel; -import net.sourceforge.ganttproject.gui.projectwizard.WizardPage; -import net.sourceforge.ganttproject.language.GanttLanguage; - -/** - * @author bard - */ -class ExporterChooserPage implements WizardPage { - private final List myExporters; - - private final State myState; - private final GanttLanguage language = GanttLanguage.getInstance(); - - ExporterChooserPage(List exporters, ExportFileWizardImpl.State state) { - myExporters = exporters; - myState = state; - - } - - @Override - public String getTitle() { - return language.getText("option.exporter.title"); - } - - @Override - public Component getComponent() { - int selectedGroupIndex = 0; - Action[] choiceChangeActions = new Action[myExporters.size()]; - GPOptionGroup[] choiceOptions = new GPOptionGroup[myExporters.size()]; - for (int i = 0; i < myExporters.size(); i++) { - final Exporter nextExporter = myExporters.get(i); - if (nextExporter == myState.getExporter()) { - selectedGroupIndex = i; - } - Action nextAction = new AbstractAction(nextExporter.getFileTypeDescription()) { - @Override - public void actionPerformed(ActionEvent e) { - ExporterChooserPage.this.myState.setExporter(nextExporter); - } - }; - GPOptionGroup nextOptions = nextExporter.getOptions(); - if (nextOptions != null) { - nextOptions.lock(); - } - choiceChangeActions[i] = nextAction; - choiceOptions[i] = nextOptions; - } - GPOptionChoicePanel choicePanel = new GPOptionChoicePanel(); - return choicePanel.getComponent(choiceChangeActions, choiceOptions, selectedGroupIndex); - } - - @Override - public void setActive(boolean b) { - if (false == b) { - for (Exporter e : myExporters) { - if (e.getOptions() != null) { - e.getOptions().commit(); - } - } - } else { - for (Exporter e : myExporters) { - if (e.getOptions() != null) { - e.getOptions().lock(); - } - } - - } - } - -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPageFx.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPageFx.kt index 692c2a9c60..f30647fb2b 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPageFx.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExporterChooserPageFx.kt @@ -20,14 +20,13 @@ package net.sourceforge.ganttproject.export import biz.ganttproject.app.* import javafx.scene.Node -import net.sourceforge.ganttproject.export.ExportFileWizardImpl.State import net.sourceforge.ganttproject.gui.projectwizard.WizardPage import java.awt.Component /** * This is the first page of the export wizard that allows the user to choose an exporter. */ -class ExporterChooserPageFx(exporters: List, private val model: State) : WizardPage { +class ExporterChooserPageFx(exporters: List, private val model: ExportWizardModel) : WizardPage { override val title: String = i18n.formatText("option.exporter.title") override val component: Component? = null @@ -61,7 +60,7 @@ class ExporterChooserPageFx(exporters: List, private val model: State) override fun setActive(b: Boolean) { if (!b) { - model.exporter.options.commit() + model.exporter?.options?.commit() } } } diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt index b5de5b5a5c..a236039ffd 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt @@ -18,15 +18,14 @@ */ package net.sourceforge.ganttproject.gui -//import biz.ganttproject.platform.getMeaningfulMessage -//import biz.ganttproject.platform.getMeaningfulMessage +import biz.ganttproject.app.FXThread import biz.ganttproject.app.PropertySheetBuilder import biz.ganttproject.app.RootLocalizer +import biz.ganttproject.app.i18n import biz.ganttproject.core.option.* import com.github.michaelbull.result.Result import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onSuccess -import javafx.beans.property.SimpleObjectProperty import javafx.embed.swing.SwingNode import javafx.scene.Node import javafx.scene.control.Label @@ -41,15 +40,14 @@ import java.awt.Component import java.io.File import javax.swing.BorderFactory import javax.swing.JFileChooser -import javax.swing.JLabel import javax.swing.JPanel +import javax.swing.SwingUtilities /** * Base class for the file chooser pages in the Import and Export wizards. */ abstract class FileChooserPageBase protected constructor( private val myDocument: Document?, - uiFacade: UIFacade?, val fileChooserTitle: String?, val pageTitle: String?, val fileChooserSelectionMode: Int = JFileChooser.FILES_ONLY, @@ -73,18 +71,12 @@ abstract class FileChooserPageBase protected constructor( } protected val fxOverwrite = ObservableBoolean("overwrite", false) abstract val preferences: Preferences - val selectedFileProperty: SimpleObjectProperty = SimpleObjectProperty(null) private val secondaryOptionsSwingNode = SwingNode() init { fxFile.addWatcher { event -> tryChosenFile(event.newValue) } -// selectedFileProperty.addListener { _, _, newValue -> -// if (fxFile.value != newValue) { -// fxFile.value = newValue -// } -// } fxOverwrite.addWatcher { tryChosenFile(fxFile.value) } } @@ -92,8 +84,18 @@ abstract class FileChooserPageBase protected constructor( override val fxComponent: Node by lazy { val root = BorderPane() root.styleClass.add("file-chooser-page") - val sheet = PropertySheetBuilder(RootLocalizer).pane { + val i18n = i18n { + default() + map(mapOf( + "file.label" to "file", + "overwrite.label" to "option.exporter.overwrite.label.trailing" + )) + } + val sheet = PropertySheetBuilder(i18n).pane { file(fxFile) { + chooserTitle = fileChooserTitle ?: "" + isSaveNotOpen = fileChooserSelectionMode != JFileChooser.FILES_ONLY + browseButtonText = RootLocalizer.formatText("fileChooser.browse") editorStyles.add("file-chooser") this@FileChooserPageBase.extensionFilters = this.extensionFilters } @@ -107,7 +109,6 @@ abstract class FileChooserPageBase protected constructor( fun showError(msg: String?) { if (msg != null) { - //UIUtil.setupErrorLabel(myFileLabel, it.newValue) root.bottom = HBox().apply { styleClass.add("alert-embedded-box") children.add(Label(msg).also { it.styleClass.add("alert-error") }) @@ -116,10 +117,12 @@ abstract class FileChooserPageBase protected constructor( root.bottom = null } } - errorMessage.addWatcher { showError(it.newValue) } + errorMessage.addWatcher { + FXThread.runLater { + showError(it.newValue) + } + } showError(errorMessage.value) - // We need to update the secondary options panel when it's created. - // In FileChooserPageBase, it's updated in setActive(true). root } @@ -146,9 +149,6 @@ abstract class FileChooserPageBase protected constructor( private val mySecondaryOptionsComponent = JPanel(BorderLayout()).also { it.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)) } - private val myFileLabel = JLabel("") -// protected val overwriteOption: BooleanOption = DefaultBooleanOption("overwrite") -// protected var hasOverwriteOption: Boolean = true protected var updateChosenFile: (File) -> File = { it } protected var proposeChosenFile: () -> File = { File(defaultFileName) } @@ -160,40 +160,14 @@ abstract class FileChooserPageBase protected constructor( override val title: String get() = pageTitle ?: "" fun tryChosenFile(file: File?) { - myFileLabel.setOpaque(true) validateFile(file).onSuccess { - //selectedFileProperty.set(file) - //UIUtil.clearErrorLabel(myFileLabel) showError(null) preferences.put(PREF_SELECTED_FILE, fxFile.value?.absolutePath) }.onFailure { showError(it ?: "Something went wrong") - //UIUtil.setupErrorLabel(myFileLabel, it) } } -// override val component: Component by lazy { -// val myComponent = JPanel(BorderLayout()) -// chooser.setFileSelectionMode(this.fileChooserSelectionMode) -// val contentPanel: JComponent = JPanel(BorderLayout()) -// val fileBox = Box.createVerticalBox() -// chooser.setAlignmentX(Component.LEFT_ALIGNMENT) -// fileBox.add(this.chooser) -// myFileLabel.setAlignmentX(Component.LEFT_ALIGNMENT) -// fileBox.add(myFileLabel) -// if (hasOverwriteOption) { -// fileBox.add( -// myOptionsBuilder.createOptionComponent( -// GPOptionGroup("exporter", this.overwriteOption), this.overwriteOption -// ) -// ) -// } -// contentPanel.add(fileBox, BorderLayout.NORTH) -// contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER) -// myComponent.add(contentPanel, BorderLayout.NORTH) -// myComponent -// } - protected open fun loadPreferences() { val oldFile = preferences.get(PREF_SELECTED_FILE, null) if (oldFile != null) { @@ -216,13 +190,13 @@ abstract class FileChooserPageBase protected constructor( for (optionGroup in optionGroups) { optionGroup.lock() } - mySecondaryOptionsComponent.removeAll() - mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH) + SwingUtilities.invokeLater { + mySecondaryOptionsComponent.removeAll() + mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH) + } secondaryOptionsSwingNode.content = mySecondaryOptionsComponent - fileFilter = createFileFilter().also { - println("filter = $it") - } + fileFilter = createFileFilter() loadPreferences() } } diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt index 2eba2b9699..c736c08ff8 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt @@ -58,7 +58,7 @@ open class WizardModel { var currentPage = 0 val pages = mutableListOf() - val needsRefresh = ObservableBoolean("", false) + val needsRefresh = ObservableBoolean("needsRefresh", false) val pageCountProperty = SimpleIntegerProperty(0) val errorMessage = ObservableString("", "").also { it.addWatcher { needsRefresh.set(true, this) } @@ -78,6 +78,10 @@ open class WizardModel { return currentPage > 0 } + fun start() { + needsRefresh.set(false, this) + } + } @@ -138,12 +142,14 @@ private class WizardUiFx(private val ctrl: DialogController, private val model: }) model.needsRefresh.addWatcher { evt -> - if (evt.newValue && evt.trigger != this) { - adjustButtonState() + if (evt.trigger != this) { + if (evt.newValue) { + adjustButtonState() + } model.needsRefresh.set(false, this) } } - + model.start() ctrl.resize() } fun show(ctrl: DialogController) { diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt index 2f1fbf9fe2..3bc5095bdd 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt @@ -46,10 +46,7 @@ class ImportFileWizard(uiFacade: UIFacade, project: IGanttProject, pluginPrefere private val wizardModel = ImporterWizardModel() init { importers.forEach { it.setContext(project, uiFacade, pluginPreferences) } - val filePage = ImportFileChooserPage(wizardModel, project, pluginPreferences, uiFacade) - filePage.fxFile.addWatcher { - wizardModel.needsRefresh.set(true, this) - } + val filePage = ImportFileChooserPage(wizardModel, project, pluginPreferences) wizardModel.importer = importers.firstOrNull() wizardModel.addPage(ImporterChooserPageFx(importers, wizardModel)) wizardModel.addPage(filePage) @@ -88,6 +85,7 @@ class ImporterWizardModel: WizardModel() { set(value) { field = value importer?.setFile(value) + needsRefresh.set(true, this) } // Some importers, e.g. ICS importer, provide a custom page that is appended to the wizard. @@ -95,11 +93,13 @@ class ImporterWizardModel: WizardModel() { init { canFinish = { - importer != null && file != null && errorMessage.value.isNullOrBlank() + (importer != null && file != null && errorMessage.value.isNullOrBlank()).also { + //println("canFinish=$it") + } } hasNext = { when (currentPage) { 0 -> importer != null - 1 -> customPageProperty.get() != null && file != null + 1 -> customPageProperty.get() != null && file != null && errorMessage.value.isNullOrBlank() else -> false } } onOk = { importer?.run() } @@ -149,8 +149,9 @@ private class ImporterChooserPageFx(importers: List, model: ImporterWi * Wizard page for choosing a file to import from. */ private class ImportFileChooserPage( - private val model: ImporterWizardModel, project: IGanttProject, private val prefs: Preferences, uiFacade: UIFacade) - : FileChooserPageBase(project.document, uiFacade, fileChooserTitle = "", + private val model: ImporterWizardModel, project: IGanttProject, private val prefs: Preferences) + : FileChooserPageBase(project.document, + fileChooserTitle = i18n.formatText("importerFileChooserPageTitle"), pageTitle = i18n.formatText("importerFileChooserPageTitle"), errorMessage = model.errorMessage) { val importer get() = model.importer