diff --git a/biz.ganttproject.app.localization b/biz.ganttproject.app.localization index ec08474a02..75b93e385d 160000 --- a/biz.ganttproject.app.localization +++ b/biz.ganttproject.app.localization @@ -1 +1 @@ -Subproject commit ec08474a02f6906b6a0aa199ce7e6c73dead565e +Subproject commit 75b93e385db8a4ba7aeeb4c87540b007351423ff diff --git a/biz.ganttproject.core/build.gradle b/biz.ganttproject.core/build.gradle index 993aa08556..1c488df352 100644 --- a/biz.ganttproject.core/build.gradle +++ b/biz.ganttproject.core/build.gradle @@ -13,6 +13,7 @@ dependencies { api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.+' api 'org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.10.+' + api 'org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.10.+' api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" api "com.google.guava:guava:31.+" api "com.michael-bull.kotlin-result:kotlin-result:2.1.0" diff --git a/biz.ganttproject.impex.ical/src/main/java/biz/ganttproject/impex/ical/IcsFileImporter.java b/biz.ganttproject.impex.ical/src/main/java/biz/ganttproject/impex/ical/IcsFileImporter.java index d799f30c06..ff6566bbaf 100644 --- a/biz.ganttproject.impex.ical/src/main/java/biz/ganttproject/impex/ical/IcsFileImporter.java +++ b/biz.ganttproject.impex.ical/src/main/java/biz/ganttproject/impex/ical/IcsFileImporter.java @@ -25,9 +25,9 @@ import net.fortuna.ical4j.data.ParserException; import net.sourceforge.ganttproject.GPLogger; import net.sourceforge.ganttproject.calendar.CalendarEditorPanel; +import net.sourceforge.ganttproject.gui.UIFacade; +import net.sourceforge.ganttproject.gui.projectwizard.WizardPage; import net.sourceforge.ganttproject.importer.ImporterBase; -import net.sourceforge.ganttproject.wizard.AbstractWizard; -import net.sourceforge.ganttproject.wizard.WizardPage; import javax.swing.*; import java.io.*; @@ -43,11 +43,11 @@ public class IcsFileImporter extends ImporterBase { private static final LoggerApi LOGGER = GPLogger.create("Import.Ics"); private static final DefaultLocalizer ourLocalizer = InternationalizationKt.getRootLocalizer(); - private final CalendarEditorPage myEditorPage; + private CalendarEditorPage myEditorPage; public IcsFileImporter() { super("impex.ics"); - myEditorPage = new CalendarEditorPage(); + myEditorPage = null; } @Override @@ -57,20 +57,23 @@ public String getFileNamePattern() { @Override public void run() { - getUiFacade().getUndoManager().undoableEdit(ourLocalizer.formatText("importCalendar"), new Runnable() { - @Override - public void run() { +// getUiFacade().getUndoManager().undoableEdit(ourLocalizer.formatText("importCalendar"), new Runnable() { +// @Override +// public void run() { List events = myEditorPage.getEvents(); if (events != null) { getProject().getActiveCalendar().setPublicHolidays(events); } - } - }); +// } +// }); } @Override public WizardPage getCustomPage() { + if (myEditorPage == null) { + myEditorPage = new CalendarEditorPage(getUiFacade()); + } return myEditorPage; } @@ -92,9 +95,15 @@ public void setFile(File file) { * Calendar editor page which wraps a {@link CalendarEditorPanel} instance */ static class CalendarEditorPage implements WizardPage { + private final UIFacade myUiFacade; private File myFile; private final JPanel myPanel = new JPanel(); private List myEvents; + + public CalendarEditorPage(UIFacade uiFacade) { + myUiFacade = uiFacade; + } + private void setFile(File f) { myFile = f; } @@ -112,12 +121,13 @@ public JComponent getComponent() { return myPanel; } - public void setActive(AbstractWizard wizard) { - if (wizard != null) { + @Override + public void setActive(boolean b) { + if (b) { myPanel.removeAll(); if (myFile != null && myFile.exists() && myFile.canRead()) { if (myEvents != null) { - myPanel.add(new CalendarEditorPanel(wizard.getUIFacade(), myEvents, null).createComponent()); + myPanel.add(new CalendarEditorPanel(myUiFacade, myEvents, null).createComponent()); return; } else { LOGGER.error("No events found in file {}", new Object[]{myFile}, Collections.emptyMap(), null); diff --git a/cloud.ganttproject.colloboque/build.gradle.kts b/cloud.ganttproject.colloboque/build.gradle.kts index 1870d8bb2d..b5604f63d3 100644 --- a/cloud.ganttproject.colloboque/build.gradle.kts +++ b/cloud.ganttproject.colloboque/build.gradle.kts @@ -34,8 +34,8 @@ repositories { } dependencies { - implementation("biz.ganttproject:biz.ganttproject.core:25.+") - implementation("biz.ganttproject:ganttproject:25.+") + implementation("biz.ganttproject:biz.ganttproject.core:26.+") + implementation("biz.ganttproject:ganttproject:26.+") implementation("org.slf4j:slf4j-api:2.0.16") implementation("ch.qos.logback:logback-classic:1.5.6") implementation("org.slf4j:jul-to-slf4j:2.0.13") diff --git a/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt b/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt index f9c8d99077..888ac748c0 100644 --- a/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt +++ b/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt @@ -245,34 +245,37 @@ object FXUtil { } } */ + fun transitionNode(node: Node, replacePane: ()->Unit, resizer: ()->Unit) { + val fadeIn = FadeTransition(Duration.seconds(0.5), node) + fadeIn.fromValue = 0.0 + fadeIn.toValue = 1.0 + + val fadeOut = FadeTransition(Duration.seconds(0.5), node) + fadeOut.fromValue = 1.0 + fadeOut.toValue = 0.1 + fadeOut.play() + //Exception("Fade out! ").printStackTrace() + fadeOut.setOnFinished { + //Exception("Fade in!").printStackTrace() + replacePane() + fadeIn.setOnFinished { + //borderPane.requestLayout() + resizer() + } + fadeIn.play() + } + } fun transitionCenterPane(borderPane: BorderPane, newCenter: javafx.scene.Node?, resizer: () -> Unit) { if (newCenter == null) { return } - val replacePane = Runnable { + val replacePane = { borderPane.center = newCenter //resizer() } if (borderPane.center == null) { - replacePane.run() + replacePane() resizer() } else { - val fadeIn = FadeTransition(Duration.seconds(0.5), borderPane) - fadeIn.fromValue = 0.0 - fadeIn.toValue = 1.0 - - val fadeOut = FadeTransition(Duration.seconds(0.5), borderPane) - fadeOut.fromValue = 1.0 - fadeOut.toValue = 0.1 - fadeOut.play() - //Exception("Fade out! ").printStackTrace() - fadeOut.setOnFinished { - //Exception("Fade in!").printStackTrace() - replacePane.run() - fadeIn.setOnFinished { - borderPane.requestLayout() - resizer() - } - fadeIn.play() - } + transitionNode(borderPane, replacePane, resizer) } } } diff --git a/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt b/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt index d72bbab643..9bd96ae524 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt @@ -20,15 +20,19 @@ package biz.ganttproject.app import biz.ganttproject.FXUtil import biz.ganttproject.centerOnOwner +import biz.ganttproject.colorFromUiManager import biz.ganttproject.lib.fx.VBoxBuilder +import biz.ganttproject.printCss import biz.ganttproject.walkTree import com.sandec.mdfx.MDFXNode import javafx.animation.FadeTransition import javafx.animation.ParallelTransition import javafx.animation.Transition import javafx.application.Platform +import javafx.embed.swing.SwingNode import javafx.event.ActionEvent import javafx.event.EventHandler +import javafx.geometry.Insets import javafx.scene.Node import javafx.scene.Parent import javafx.scene.control.* @@ -51,6 +55,7 @@ import net.sourceforge.ganttproject.gui.UIFacade import java.util.Stack import java.util.concurrent.CountDownLatch import javax.swing.SwingUtilities +import kotlin.math.max /** * Some utility code for building nice dialogs. Provides the following features: @@ -88,6 +93,7 @@ fun dialogFxBuild(owner: Window? = null, id: String? = null, contentBuilder: (Di dialogPane.stylesheets.addAll(DIALOG_STYLESHEET) dialogBuildApi.setEscCloseEnabled(true) contentBuilder(dialogBuildApi) + when (dialogBuildApi.frameStyle) { FrameStyle.NATIVE_FRAME -> initStyle(StageStyle.DECORATED) FrameStyle.NO_FRAME -> initStyle(StageStyle.UNDECORATED) @@ -127,6 +133,7 @@ fun dialogFxBuild(owner: Window? = null, id: String? = null, contentBuilder: (Di } class DialogPaneExt : DialogPane() { + lateinit var buttonBar: HBox private lateinit var errorPaneWrapper: StackPane fun setButtonBarNode(node: Node) { @@ -137,16 +144,21 @@ class DialogPaneExt : DialogPane() { errorPaneWrapper.isManaged = true } override fun createButtonBar(): Node { - val buttonBar = StackPane(super.createButtonBar()) + val buttonBar = StackPane(super.createButtonBar()).also { + it.minWidth = Region.USE_PREF_SIZE + } errorPaneWrapper = StackPane().also { - it.styleClass.addAll("swing-background", "hide") + it.styleClass.addAll("hide") it.isManaged = false } return HBox().apply { - styleClass.addAll("button-pane", "swing-background") + styleClass.addAll("button-pane") + minWidth = Region.USE_PREF_SIZE HBox.setHgrow(buttonBar, Priority.ALWAYS) HBox.setHgrow(errorPaneWrapper, Priority.SOMETIMES) children.addAll(errorPaneWrapper, buttonBar) + }.also { + this.buttonBar = it } } } @@ -431,7 +443,9 @@ class DialogControllerFx(private val dialogPane: DialogPaneExt, private val dial } } override var onClosed: () -> Unit = {} - private val stackPane = StackPane().also { it.styleClass.add("layers") } + private val stackPane = StackPane().also { + it.styleClass.add("layers") + } private var content: Node = Region() private var cancelAction: CancelAction? = null @@ -497,6 +511,8 @@ class DialogControllerFx(private val dialogPane: DialogPaneExt, private val dial } override fun resize() { + dialogPane.minWidth = dialogPane.width + dialogPane.buttonBar.minWidth = max(dialogPane.buttonBar.minWidth, dialogPane.width) dialogPane.layout() dialogPane.scene?.window?.sizeToScene() } @@ -746,4 +762,65 @@ object DialogPlacement { } const val DIALOG_STYLESHEET = "/biz/ganttproject/app/Dialog.css" +private val SWING_BACKGROUND_STYLES = setOf("tab-header-background", "tab-contents", "swing-background") +fun DialogController.setSwingBackground() { + val background = Background( + BackgroundFill( + "Panel.background".colorFromUiManager(), CornerRadii.EMPTY, Insets.EMPTY + ) + ) + + walkTree { + if (it is Region && it.styleClass.intersect(SWING_BACKGROUND_STYLES).isNotEmpty()) { + it.background = background + } + } +} +fun main() { + class PureFxTestApp : javafx.application.Application() { + override fun start(primaryStage: javafx.stage.Stage) { + val btnOpen = Button("Open Pure JavaFX Dialog") + btnOpen.onAction = EventHandler { + val dialog = Dialog() + dialog.title = "Pure JavaFX Layout Test" + + val pane = object : DialogPane() { + override fun createButtonBar(): Node { + // This mimics the structure in DialogPaneExt + val buttonBar = super.createButtonBar() + val errorPaneWrapper = StackPane().apply { + isManaged = false + isVisible = false + } + return HBox(10.0).apply { + styleClass.add("button-pane") + HBox.setHgrow(buttonBar, Priority.ALWAYS) + children.addAll(errorPaneWrapper, buttonBar) + } + } + } + +// pane.content = Label("This dialog uses pure JavaFX components.\nCheck if the buttons below are clipped.").apply { +// padding = Insets(20.0) +//// minWidth = 200.0 +// } + pane.content = StackPane()//SwingNode() + + pane.buttonTypes.addAll( + ButtonType.OK, + ButtonType.APPLY, + ButtonType.CANCEL, + ButtonType("Very Long Button Label") + ) + + dialog.dialogPane = pane + dialog.showAndWait() + } + + primaryStage.scene = javafx.scene.Scene(StackPane(btnOpen), 300.0, 200.0) + primaryStage.show() + } + } + javafx.application.Application.launch(PureFxTestApp::class.java) +} \ No newline at end of file diff --git a/ganttproject/src/main/java/biz/ganttproject/storage/StorageDialogBuilder.kt b/ganttproject/src/main/java/biz/ganttproject/storage/StorageDialogBuilder.kt index 9acc420413..c88e75a42b 100644 --- a/ganttproject/src/main/java/biz/ganttproject/storage/StorageDialogBuilder.kt +++ b/ganttproject/src/main/java/biz/ganttproject/storage/StorageDialogBuilder.kt @@ -123,11 +123,15 @@ class StorageDialogBuilder( } fun build(mode: Mode) { - dialogBuildApi.addStyleClass("dlg-storage") + dialogBuildApi.addStyleClass("dlg", "dlg-storage") + dialogBuildApi.addStyleSheet("/biz/ganttproject/app/Dialog.css") dialogBuildApi.addStyleSheet("/biz/ganttproject/storage/StorageDialog.css") dialogBuildApi.addStyleSheet("/biz/ganttproject/storage/StorageDialog2.css") dialogBuildApi.removeButtonBar() dialogBuildApi.setEscCloseEnabled(true) + dialogBuildApi.onShown = { + dialogBuildApi.resize() + } val contentPane = BorderPane() contentPane.styleClass.addAll("body", "pane-storage") diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectExportAction.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectExportAction.java deleted file mode 100644 index ca592bd38a..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectExportAction.java +++ /dev/null @@ -1,60 +0,0 @@ -/* -GanttProject is an opensource project management tool. -Copyright (C) 2005-2012 GanttProject Team - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.action.project; - -import net.sourceforge.ganttproject.IGanttProject; -import net.sourceforge.ganttproject.action.GPAction; -import net.sourceforge.ganttproject.export.ExportFileWizardImpl; -import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.gui.projectwizard.WizardImpl; -import org.osgi.service.prefs.Preferences; - -import java.awt.event.ActionEvent; - -/** - * @author bard - */ -public class ProjectExportAction extends GPAction { - private final IGanttProject myProject; - - private final UIFacade myUIFacade; - - private Preferences myPluginPrerences; - - public ProjectExportAction(UIFacade uiFacade, IGanttProject project, Preferences pluginPrerences) { - super("project.export"); - myProject = project; - myUIFacade = uiFacade; - myPluginPrerences = pluginPrerences; - } - - @Override - protected String getIconFilePrefix() { - return "export_"; - } - - @Override - public void actionPerformed(ActionEvent e) { - if (calledFromAppleScreenMenu(e)) { - return; - } - WizardImpl wizard = new ExportFileWizardImpl(myUIFacade, myProject, myPluginPrerences); - wizard.show(); - } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectImportAction.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectImportAction.java deleted file mode 100644 index ccacea7e93..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectImportAction.java +++ /dev/null @@ -1,57 +0,0 @@ -/* -GanttProject is an opensource project management tool. -Copyright (C) 2005-2011 GanttProject Team - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.action.project; - -import net.sourceforge.ganttproject.GanttProject; -import net.sourceforge.ganttproject.action.GPAction; -import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.importer.ImportFileWizardImpl; -import net.sourceforge.ganttproject.wizard.AbstractWizard; - -import java.awt.event.ActionEvent; - -/** - * @author bard - */ -public class ProjectImportAction extends GPAction { - - private final UIFacade myUIFacade; - - private final GanttProject myProject; - - public ProjectImportAction(UIFacade uiFacade, GanttProject project) { - super("project.import"); - myUIFacade = uiFacade; - myProject = project; - } - - @Override - public void actionPerformed(ActionEvent e) { - if (calledFromAppleScreenMenu(e)) { - return; - } - AbstractWizard wizard = new ImportFileWizardImpl(myUIFacade, myProject, myProject.getGanttOptions()); - wizard.show(); - } - - @Override - protected String getIconFilePrefix() { - return "import_"; - } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectMenu.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectMenu.kt index 1423b54e6a..06faf31c05 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectMenu.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/action/project/ProjectMenu.kt @@ -28,6 +28,8 @@ import net.sourceforge.ganttproject.GanttProject import net.sourceforge.ganttproject.IGanttProject import net.sourceforge.ganttproject.action.GPAction import net.sourceforge.ganttproject.document.webdav.WebDavStorageImpl +import net.sourceforge.ganttproject.export.ExportFileWizardImpl +import net.sourceforge.ganttproject.importer.ImportFileWizard import javax.swing.Action import javax.swing.JMenu import javax.swing.JMenuItem @@ -53,9 +55,14 @@ class ProjectMenu(project: GanttProject, window: Window, key: String) : JMenu(GP ) private val projectSettingsAction = ProjectPropertiesAction(project.project, project.uiFacade) - private val importAction = ProjectImportAction(project.uiFacade, project) - private val exportAction = ProjectExportAction( - project.uiFacade, project, project.ganttOptions.pluginPreferences) + private val importAction = GPAction.create("project.import") { + ImportFileWizard(project.uiFacade, project, + project.ganttOptions.pluginPreferences.node("/instance/net.sourceforge.ganttproject/import") + ).show() + } + private val exportAction = GPAction.create("project.export") { + ExportFileWizardImpl(project.uiFacade, project, project.ganttOptions.pluginPreferences).show() + } //private val printAction = PrintAction(project) //private val printPreviewAction = ProjectPreviewAction(project) private val printAction = createPrintAction(project.uiFacade, project.ganttOptions.pluginPreferences) diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/CommandLineExportApplication.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/CommandLineExportApplication.java index 4dae60f0b9..3fca8ef5ea 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/CommandLineExportApplication.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/CommandLineExportApplication.java @@ -86,7 +86,7 @@ private boolean export(Exporter exporter, Args args, IGanttProject project, UIFa // } Job.getJobManager().setProgressProvider(new ConsoleProgressProvider()); - File outputFile = args.outputFile == null ? FileChooserPage.proposeOutputFile(project, exporter) + File outputFile = args.outputFile == null ? ExportFileChooserPageKt.proposeOutputFile(project, exporter) : args.outputFile; Preferences prefs = new PluginPreferencesImpl(null, ""); diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt new file mode 100644 index 0000000000..fefb1b8702 --- /dev/null +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileChooserPage.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2011-2026 Dmitry Barashev, BarD Software s.r.o. + * + * This file is part of GanttProject, an open-source 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 biz.ganttproject.app.RootLocalizer +import biz.ganttproject.core.option.GPOptionGroup +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 myProject: IGanttProject, + prefs: Preferences, + uiFacade: UIFacade? +) : FileChooserPageBase( + prefs, myProject.document, uiFacade, + fileChooserTitle = i18n.formatText("selectFileToExport"), + fileChooserSelectionMode = JFileChooser.FILES_AND_DIRECTORIES, + pageTitle = i18n.formatText("selectFileToExport") +) { + private val myWebPublishingGroup: GPOptionGroup = GPOptionGroup( + "exporter.webPublishing", myState.publishInWebOption + ).also { it.isTitled = false } + + init { + updateChosenFile = { replaceExtension(it, myState.exporter.proposeFileExtension()) } + proposeChosenFile = { + proposeOutputFile(myProject, myState.exporter) ?: File(defaultFileName) + } + overwriteOption.addChangeValueListener { tryChosenFile(myState.file) } + } + + override fun validateFile(file: File?): Result { + if (file == null) { + return Err("File cannot be null") + } + + overwriteOption.getIsWritableProperty().set(false, this) + if (!file.exists()) { + val parent = file.getParentFile() + if (!parent.exists()) { + return Err( + i18n.formatText( + "fileChooser.error.directoryDoesNotExists", UIUtil.formatPathForLabel(parent) + ) + ) + } + if (!parent.canWrite()) { + return Err( + i18n.formatText( + "fileChooser.error.directoryIsReadOnly", UIUtil.formatPathForLabel(parent) + ) + ) + } + } else if (!file.canWrite()) { + if (file.isDirectory()) { + return Err( + i18n.formatText( + "fileChooser.error.directoryIsReadOnly", UIUtil.formatPathForLabel(file) + ) + ) + } else { + return Err( + i18n.formatText("fileChooser.error.fileIsReadOnly", UIUtil.formatPathForLabel(file)), + ) + } + } else { + overwriteOption.getIsWritableProperty().set(true, this) + if (!overwriteOption.isChecked()) { + return Err(i18n.formatText("fileChooser.warning.fileExists")) + } + } + return Ok(file) + } + + override fun createSecondaryOptionsPanel(): Component { + return myState.exporter.getCustomOptionsUI() ?: super.createSecondaryOptionsPanel() + } + + override fun createFileFilter(): FileFilter = + ExtensionBasedFileFilter( + myState.exporter.getFileNamePattern(), myState.exporter.getFileTypeDescription() + ) + + override val optionGroups: List + get() = listOf(myWebPublishingGroup) + (myState.exporter?.secondaryOptions ?: emptyList()) + +} + +fun proposeOutputFile(project: IGanttProject, exporter: Exporter): File? { + val proposedExtension = exporter.proposeFileExtension() ?: return null + + var result: File? = null + val projectDocument = project.document + if (projectDocument != null) { + val localDocument = projectDocument.asLocalDocument() + if (localDocument != null) { + val localFile = localDocument.file + if (localFile.exists()) { + result = replaceExtension(localFile, proposedExtension) + } else { + val directory = localFile.getParentFile() + if (directory.exists()) { + result = File(directory, project.projectName + "." + proposedExtension) + } + } + } else { + result = File( + getDefaultLocalFolder(), replaceExtension(projectDocument.getFileName(), proposedExtension) + ) + } + } + if (result == null) { + val userHome = File(System.getProperty("user.home")) + result = File(userHome, project.projectName + "." + proposedExtension) + } + return result +} + +private val i18n = RootLocalizer \ No newline at end of file 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 1b5c086e4f..8cf11c358d 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/ExportFileWizardImpl.java @@ -19,12 +19,16 @@ 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.net.URLDecoder; import java.util.List; import javax.swing.SwingUtilities; +import biz.ganttproject.app.InternationalizationKt; +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; @@ -34,13 +38,12 @@ of the License, or (at your option) any later version. import net.sourceforge.ganttproject.GPLogger; import net.sourceforge.ganttproject.IGanttProject; import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.gui.projectwizard.WizardImpl; import net.sourceforge.ganttproject.plugins.PluginManager; /** * @author bard */ -public class ExportFileWizardImpl extends WizardImpl { +public class ExportFileWizardImpl { private final IGanttProject myProject; @@ -48,9 +51,12 @@ public class ExportFileWizardImpl extends WizardImpl { private static Exporter ourLastSelectedExporter; private static List ourExporters; + private final WizardModel wizardModel = new WizardModel(); public ExportFileWizardImpl(UIFacade uiFacade, IGanttProject project, Preferences pluginPreferences) { - super(uiFacade, language.getText("exportWizard.dialog.title"), "wizard.export"); + wizardModel.setTitle(InternationalizationKt.getRootLocalizer().formatText("exportWizard.dialog.title")); + wizardModel.setDialogId("wizard.export"); + final Preferences exportNode = pluginPreferences.node("/instance/net.sourceforge.ganttproject/export"); myProject = project; myState = new State(); @@ -68,33 +74,38 @@ public void changeValue(ChangeValueEvent event) { for (Exporter e : ourExporters) { e.setContext(project, uiFacade, pluginPreferences); } - addPage(new ExporterChooserPage(ourExporters, myState)); - addPage(new FileChooserPage(myState, myProject, this, exportNode)); + + var fileChooserPage = new ExportFileChooserPage(myState, myProject, exportNode, uiFacade); + wizardModel.addPage(new ExporterChooserPage(ourExporters, myState)); + wizardModel.addPage(fileChooserPage); + wizardModel.setOnOk(() -> { + onOkPressed(); + return Unit.INSTANCE; + }); + wizardModel.setCanFinish(this::canFinish); } - @Override protected boolean canFinish() { return myState.getExporter() != null && myState.myUrl != null && "file".equals(myState.getUrl().getProtocol()); } - @Override protected void onOkPressed() { - super.onOkPressed(); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - try { - ExportFinalizationJob finalizationJob = new ExportFinalizationJobImpl(); - if ("file".equals(myState.getUrl().getProtocol())) { - myState.getExporter().run(new File(myState.getUrl().toURI()), finalizationJob); - } - } catch (Exception e) { - GPLogger.log(e); + SwingUtilities.invokeLater(() -> { + try { + ExportFinalizationJob finalizationJob = new ExportFinalizationJobImpl(); + if ("file".equals(myState.getUrl().getProtocol())) { + myState.getExporter().run(new File(myState.getUrl().toURI()), finalizationJob); } + } catch (Exception e) { + GPLogger.log(e); } }); } + public void show() { + WizardImplFxKt.showWizard(wizardModel); + } + private class ExportFinalizationJobImpl implements ExportFinalizationJob { @Override public void run(File[] exportedFiles) { @@ -111,6 +122,7 @@ static class State { private final BooleanOption myPublishInWebOption = new DefaultBooleanOption("exporter.publishInWeb"); private URL myUrl; + private File myFile; void setExporter(Exporter exporter) { myExporter = exporter; @@ -125,12 +137,25 @@ BooleanOption getPublishInWebOption() { return myPublishInWebOption; } - void setUrl(URL url) { - myUrl = url; - } - 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/FileChooserPage.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/export/FileChooserPage.java deleted file mode 100644 index d977659ebd..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/export/FileChooserPage.java +++ /dev/null @@ -1,206 +0,0 @@ -/* -GanttProject is an opensource project management tool. License: GPL3 -Copyright (C) 2011 GanttProject Team - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.export; - -import biz.ganttproject.app.InternationalizationKt; -import biz.ganttproject.core.option.GPOption; -import biz.ganttproject.core.option.GPOptionGroup; -import biz.ganttproject.storage.DocumentKt; -import net.sourceforge.ganttproject.IGanttProject; -import net.sourceforge.ganttproject.document.Document; -import net.sourceforge.ganttproject.document.FileDocument; -import net.sourceforge.ganttproject.export.ExportFileWizardImpl.State; -import net.sourceforge.ganttproject.filter.ExtensionBasedFileFilter; -import net.sourceforge.ganttproject.gui.FileChooserPageBase; -import net.sourceforge.ganttproject.gui.UIUtil; -import net.sourceforge.ganttproject.gui.projectwizard.WizardImpl; -import net.sourceforge.ganttproject.language.GanttLanguage; -import net.sourceforge.ganttproject.util.FileUtil; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.osgi.service.prefs.Preferences; - -import javax.swing.filechooser.FileFilter; -import java.awt.*; -import java.io.File; -import java.net.URL; -import java.text.MessageFormat; -import java.util.List; - -class FileChooserPage extends FileChooserPageBase { - - private final State myState; - - private final IGanttProject myProject; - - private final GPOptionGroup myWebPublishingGroup; - - FileChooserPage(State state, IGanttProject project, WizardImpl wizardImpl, Preferences prefs) { - super(wizardImpl, prefs); - myState = state; - myProject = project; - myWebPublishingGroup = new GPOptionGroup("exporter.webPublishing", new GPOption[] { state.getPublishInWebOption() }); - myWebPublishingGroup.setTitled(false); - getOverwriteOption().addChangeValueListener(evt -> tryChosenFile(getChooser().getFile())); - } - - @Override - protected String getFileChooserTitle() { - return GanttLanguage.getInstance().getText("selectFileToExport"); - } - - @Override - public String getTitle() { - return GanttLanguage.getInstance().getText("selectFileToExport"); - } - - @Override - protected void loadPreferences() { - super.loadPreferences(); - if (getPreferences().get(PREF_SELECTED_FILE, null) == null) { - getChooser().setFile(proposeOutputFile(myProject, myState.getExporter())); - } else { - String proposedExtension = myState.getExporter().proposeFileExtension(); - if (proposedExtension != null) { - File selectedFile = getChooser().getFile(); - String fileName = selectedFile.getName(); - int lastDot = fileName.lastIndexOf('.'); - if (lastDot < 0) { - // No extension available, add one - fileName += "."; - lastDot = selectedFile.getName().length(); - } - String extension = fileName.substring(lastDot + 1); - if (!extension.equals(proposedExtension)) { - getChooser().setFile( - new File(selectedFile.getParent(), fileName.substring(0, lastDot + 1) + proposedExtension)); - } - } - } - } - - @Override - protected void onSelectedUrlChange(URL selectedUrl) { - myState.setUrl(selectedUrl); - super.onSelectedUrlChange(selectedUrl); - } - - @Override - protected IStatus onSelectedFileChange(File file) { - getOverwriteOption().getIsWritableProperty().set(false, this); - if (!file.exists()) { - File parent = file.getParentFile(); - if (!parent.exists()) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, - GanttLanguage.getInstance().formatText("fileChooser.error.directoryDoesNotExists", UIUtil.formatPathForLabel(parent)), - null); - } - if (!parent.canWrite()) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, - GanttLanguage.getInstance().formatText("fileChooser.error.directoryIsReadOnly", UIUtil.formatPathForLabel(parent)), - null); - } - } else if (!file.canWrite()) { - if (file.isDirectory()) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, - GanttLanguage.getInstance().formatText("fileChooser.error.directoryIsReadOnly", UIUtil.formatPathForLabel(file)), - null); - } else { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, - GanttLanguage.getInstance().formatText("fileChooser.error.fileIsReadOnly", UIUtil.formatPathForLabel(file)), - null); - } - } else { - getOverwriteOption().getIsWritableProperty().set(true, this); - if (!getOverwriteOption().isChecked()) { - return new Status(IStatus.WARNING, "foo", IStatus.WARNING, - InternationalizationKt.getRootLocalizer().formatText("fileChooser.warning.fileExists"), null); - } - } - IStatus result = new Status(IStatus.OK, "foo", IStatus.OK, "", null); - String proposedExtension = myState.getExporter().proposeFileExtension(); - if (proposedExtension != null) { - if (false == file.getName().toLowerCase().endsWith(proposedExtension)) { - result = new Status(IStatus.OK, "foo", IStatus.OK, MessageFormat.format("Note that the extension is not {0}", - new Object[] { proposedExtension }), null); - } - } - IStatus setStatus = setSelectedFile(file); - return setStatus.isOK() ? result : setStatus; - } - - @Override - protected Component createSecondaryOptionsPanel() { - Component customUI = myState.getExporter().getCustomOptionsUI(); - return customUI == null ? super.createSecondaryOptionsPanel() : customUI; - } - - static File proposeOutputFile(IGanttProject project, Exporter exporter) { - String proposedExtension = exporter.proposeFileExtension(); - if (proposedExtension == null) { - return null; - } - - File result = null; - Document projectDocument = project.getDocument(); - if (projectDocument != null) { - FileDocument localDocument = DocumentKt.asLocalDocument(projectDocument); - if (localDocument != null) { - File localFile = localDocument.getFile(); - if (localFile.exists()) { - result = FileUtil.INSTANCE.replaceExtension(localFile, proposedExtension); - } else { - File directory = localFile.getParentFile(); - if (directory.exists()) { - result = new File(directory, project.getProjectName() + "." + proposedExtension); - } - } - } else { - result = new File(DocumentKt.getDefaultLocalFolder(), FileUtil.replaceExtension(projectDocument.getFileName(), proposedExtension)); - } - } - if (result == null) { - File userHome = new File(System.getProperty("user.home")); - result = new File(userHome, project.getProjectName() + "." + proposedExtension); - } - return result; - } - - @Override - protected FileFilter createFileFilter() { - return new ExtensionBasedFileFilter(myState.getExporter().getFileNamePattern(), - myState.getExporter().getFileTypeDescription()); - } - - @Override - protected GPOptionGroup[] getOptionGroups() { - GPOptionGroup[] exporterOptions = null; - if (myState.getExporter() != null) { - List options = myState.getExporter().getSecondaryOptions(); - exporterOptions = options == null ? null : options.toArray(new GPOptionGroup[0]); - } - if (exporterOptions == null) { - return new GPOptionGroup[] { myWebPublishingGroup }; - } - GPOptionGroup[] result = new GPOptionGroup[exporterOptions.length + 1]; - result[0] = myWebPublishingGroup; - System.arraycopy(exporterOptions, 0, result, 1, exporterOptions.length); - return result; - } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.java deleted file mode 100644 index 51b251540c..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.java +++ /dev/null @@ -1,217 +0,0 @@ -/* -GanttProject is an opensource project management tool. -Copyright (C) 2011 GanttProject team - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.gui; - -import biz.ganttproject.app.InternationalizationKt; -import biz.ganttproject.core.option.BooleanOption; -import biz.ganttproject.core.option.DefaultBooleanOption; -import biz.ganttproject.core.option.GPOptionGroup; -import net.sourceforge.ganttproject.document.Document; -import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder; -import net.sourceforge.ganttproject.gui.projectwizard.WizardImpl; -import net.sourceforge.ganttproject.gui.projectwizard.WizardPage; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.osgi.service.prefs.Preferences; - -import javax.swing.*; -import javax.swing.filechooser.FileFilter; -import java.awt.*; -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; - -public abstract class FileChooserPageBase implements WizardPage { - protected static final String PREF_SELECTED_FILE = "selected_file"; - - private TextFieldAndFileChooserComponent myChooser; - private final OptionsPageBuilder myOptionsBuilder; - private final JPanel mySecondaryOptionsComponent; - private final WizardImpl myWizard; - private final JLabel myFileLabel = new JLabel(""); - private BooleanOption myOverwriteOption = new DefaultBooleanOption("overwrite"); - - private final Preferences myPreferences; - - protected FileChooserPageBase(WizardImpl wizard, Preferences prefs) { - myPreferences = prefs; - myWizard = wizard; - myOptionsBuilder = new OptionsPageBuilder(); - mySecondaryOptionsComponent = new JPanel(new BorderLayout()); - mySecondaryOptionsComponent.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); - myOptionsBuilder.setI18N(new OptionsPageBuilder.I18N() { - @Override - protected boolean hasValue(String key) { - return (key.equals(getCanonicalOptionLabelKey(myOverwriteOption) + ".trailing")) ? true : super.hasValue(key); - } - - @Override - protected String getValue(String key) { - if (key.equals(getCanonicalOptionLabelKey(myOverwriteOption) + ".trailing")) { - return InternationalizationKt.getRootLocalizer().formatText("document.overwrite"); - } - return super.getValue(key); - } - }); - - } - - protected abstract String getFileChooserTitle(); - - protected BooleanOption getOverwriteOption() { - return myOverwriteOption; - } - /** @return a default export filename */ - protected String getDefaultFileName() { - Document document = myWizard.getUIFacade().getGanttChart().getProject().getDocument(); - if (document == null) { - return "document.gan"; - } - return document.getFileName(); - } - - protected int getFileChooserSelectionMode() { - return JFileChooser.FILES_AND_DIRECTORIES; - } - - protected void tryChosenFile(File file) { - IStatus status = FileChooserPageBase.this.onSelectedFileChange(file); - if (!status.isOK()) { - onSelectedUrlChange(null); - } - FileChooserPageBase.setStatus(myFileLabel, status); - } - - @Override - public Component getComponent() { - JPanel myComponent = new JPanel(new BorderLayout()); - myChooser = new TextFieldAndFileChooserComponent(myWizard.getUIFacade(), getFileChooserTitle()) { - @Override - protected void onFileChosen(File file) { - tryChosenFile(file); - } - }; - myChooser.setFileSelectionMode(getFileChooserSelectionMode()); - JComponent contentPanel = new JPanel(new BorderLayout()); - Box fileBox = Box.createVerticalBox(); - myChooser.setAlignmentX(Component.LEFT_ALIGNMENT); - fileBox.add(myChooser); - myFileLabel.setAlignmentX(Component.LEFT_ALIGNMENT); - fileBox.add(myFileLabel); - fileBox.add(myOptionsBuilder.createOptionComponent(new GPOptionGroup("exporter", myOverwriteOption), myOverwriteOption)); - contentPanel.add(fileBox, BorderLayout.NORTH); - contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER); - myComponent.add(contentPanel, BorderLayout.NORTH); - return myComponent; - } - - protected void loadPreferences() { - String oldFile = myPreferences.get(FileChooserPageBase.PREF_SELECTED_FILE, null); - if (oldFile != null) { - // Use the previously used path with the current filename for the default - // name - // The implementing classes can modify the file extension when desired - String oldPath = new File(oldFile).getParent(); - File f = new File(oldPath, getDefaultFileName()); - myChooser.setFile(f); - } - } - - @Override - public void setActive(boolean b) { - GPOptionGroup[] optionGroups = getOptionGroups(); - if (b == false) { - for (GPOptionGroup optionGroup : optionGroups) { - optionGroup.commit(); - } - if (myChooser.getFile() != null) { - myPreferences.put(FileChooserPageBase.PREF_SELECTED_FILE, myChooser.getFile().getAbsolutePath()); - } - } else { - for (GPOptionGroup optionGroup : optionGroups) { - optionGroup.lock(); - } - if (mySecondaryOptionsComponent != null) { - mySecondaryOptionsComponent.removeAll(); - } - mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH); - myChooser.setFileFilter(createFileFilter()); - loadPreferences(); - onSelectedUrlChange(getSelectedUrl()); - myWizard.getDialog().layout(); - } - } - - protected Component createSecondaryOptionsPanel() { - return myOptionsBuilder.buildPlanePage(getOptionGroups()); - } - - protected final Preferences getPreferences() { - return myPreferences; - } - - protected final TextFieldAndFileChooserComponent getChooser() { - return myChooser; - } - - private URL getSelectedUrl() { - return myChooser.getSelectedURL(); - } - - protected abstract FileFilter createFileFilter(); - - protected abstract GPOptionGroup[] getOptionGroups(); - - protected void onSelectedUrlChange(URL selectedUrl) { - myWizard.adjustButtonState(); - } - - protected IStatus setSelectedFile(File file) { - try { - onSelectedUrlChange(file.toURI().toURL()); - return new Status(IStatus.OK, "foo", IStatus.OK, " ", null); - } catch (MalformedURLException e) { - e.printStackTrace(); - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, e.getMessage(), null); - } - } - - protected IStatus onSelectedFileChange(File file) { - if (file == null) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File does not exist", null); - } - if (!file.exists()) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File does not exist", null); - } - if (!file.canRead()) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File read error", null); - } - return setSelectedFile(file); - } - - private static void setStatus(JLabel label, IStatus status) { - label.setOpaque(true); - if (status.isOK()) { - UIUtil.clearErrorLabel(label); - label.setText(status.getMessage()); - } else { - UIUtil.setupErrorLabel(label, status.getMessage()); - } - } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt new file mode 100644 index 0000000000..da847aa5b4 --- /dev/null +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/FileChooserPageBase.kt @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2011-2026 Dmitry Barashev, BarD Software s.r.o. + * + * This file is part of GanttProject, an open-source 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.gui + +import biz.ganttproject.app.RootLocalizer +import biz.ganttproject.core.option.BooleanOption +import biz.ganttproject.core.option.DefaultBooleanOption +import biz.ganttproject.core.option.GPOptionGroup +import com.github.michaelbull.result.Result +import com.github.michaelbull.result.onFailure +import com.github.michaelbull.result.onSuccess +import javafx.beans.property.SimpleObjectProperty +import net.sourceforge.ganttproject.document.Document +import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder +import net.sourceforge.ganttproject.gui.projectwizard.WizardPage +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 + +/** + * Base class for the file chooser pages in the Import and Export wizards. + */ +abstract class FileChooserPageBase protected constructor( + protected val preferences: Preferences, + private val myDocument: Document?, + uiFacade: UIFacade?, + val fileChooserTitle: String?, + val pageTitle: String?, + val fileChooserSelectionMode: Int = JFileChooser.FILES_ONLY +) : WizardPage { + protected val chooser: TextFieldAndFileChooserComponent = object : TextFieldAndFileChooserComponent(uiFacade, this.fileChooserTitle) { + override fun onFileChosen(file: File?) { + tryChosenFile(file) + } + } + 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 var hasOverwriteOption: Boolean = true + protected var updateChosenFile: (File) -> File = { it } + protected var proposeChosenFile: () -> File = { File(defaultFileName) } + + protected val defaultFileName: String + get() = if (myDocument == null) "document.gan" else myDocument.getFileName() + + + override fun getTitle(): String? = pageTitle + + fun tryChosenFile(file: File?) { + myFileLabel.setOpaque(true) + validateFile(file).onSuccess { + selectedFileProperty.set(file) + UIUtil.clearErrorLabel(myFileLabel) + preferences.put(PREF_SELECTED_FILE, chooser.file.absolutePath) + }.onFailure { + UIUtil.setupErrorLabel(myFileLabel, it) + } + } + + override fun getComponent(): Component { + 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) + return myComponent + } + + protected open fun loadPreferences() { + val oldFile = preferences.get(PREF_SELECTED_FILE, null) + if (oldFile != null) { + chooser.setFile(updateChosenFile(File(oldFile))) + } else { + chooser.setFile(proposeChosenFile()) + } + } + + override fun setActive(isActive: Boolean) { + val optionGroups = this.optionGroups + if (isActive == false) { + for (optionGroup in optionGroups) { + optionGroup.commit() + } + if (chooser.file != null) { + preferences.put(PREF_SELECTED_FILE, chooser.file.absolutePath) + } + } else { + for (optionGroup in optionGroups) { + optionGroup.lock() + } + mySecondaryOptionsComponent.removeAll() + mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH) + chooser.setFileFilter(createFileFilter()) + loadPreferences() + } + } + + protected open fun createSecondaryOptionsPanel(): Component { + return myOptionsBuilder.buildPlanePage(this.optionGroups.toTypedArray()) + } + + protected abstract fun createFileFilter(): FileFilter? + + protected abstract val optionGroups: List + + protected open fun validateFile(file: File?): Result { + return basicValidateFile(file) + } +} + +const val PREF_SELECTED_FILE: String = "selected_file" diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/ImpexWizard.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/ImpexWizard.kt new file mode 100644 index 0000000000..6e4237d9b1 --- /dev/null +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/ImpexWizard.kt @@ -0,0 +1,19 @@ +package net.sourceforge.ganttproject.gui + +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.Result +import java.io.File + +internal fun basicValidateFile(file: File?): Result { + if (file == null) { + return Err("File does not exist"); + } + if (!file.exists()) { + return Err("File does not exist") + } + if (!file.canRead()) { + return Err("File read error"); + } + return Ok(file) +} \ No newline at end of file diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/PropertiesDialog.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/PropertiesDialog.kt index 87eb91b80c..2526a35287 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/PropertiesDialog.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/PropertiesDialog.kt @@ -21,19 +21,13 @@ package net.sourceforge.ganttproject.gui import biz.ganttproject.app.DialogController import biz.ganttproject.app.ErrorPane import biz.ganttproject.app.dialog +import biz.ganttproject.app.setSwingBackground import javafx.collections.ObservableList -import javafx.geometry.Insets -import javafx.scene.control.ButtonBar -import javafx.scene.control.TabPane -import javafx.scene.layout.Background -import javafx.scene.layout.BackgroundFill -import javafx.scene.layout.CornerRadii -import javafx.scene.layout.Region -import net.sourceforge.ganttproject.action.GPAction -import biz.ganttproject.colorFromUiManager import javafx.embed.swing.SwingNode import javafx.scene.control.Tab +import javafx.scene.control.TabPane import javafx.scene.layout.StackPane +import net.sourceforge.ganttproject.action.GPAction import javax.swing.JComponent import javax.swing.SwingUtilities @@ -101,16 +95,7 @@ fun propertiesDialog(title: String, id: String, actions: List, validat // This is hack for the dialogs showing Swing components. We set the background color of the dialog panes // to be the same as the Panel background in the current Swing LAF. dialogController.onShown = { - dialogController.walkTree { node -> - if (node is ButtonBar - || node.styleClass.intersect(listOf("tab-header-background", "tab-contents", "swing-background")).isNotEmpty()) { - - (node as Region).background = - Background(BackgroundFill( - "Panel.background".colorFromUiManager(), CornerRadii.EMPTY, Insets.EMPTY - )) - } - } + dialogController.setSwingBackground() tabProviders.first().requestFocus() dialogController.resize() } diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/UIUtil.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/UIUtil.java index ea6c6f02aa..298a01a99e 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/UIUtil.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/UIUtil.java @@ -541,6 +541,7 @@ public static void setupErrorLabel(JLabel label, String errorMessage) { public static void clearErrorLabel(JLabel label) { label.setIcon(null); label.setForeground(UIManager.getColor("Label.foreground")); + label.setText(""); } public static void initJavaFx(final Runnable andThen) { diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/GPOptionChoicePanel.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/GPOptionChoicePanel.java index b78b2366bf..69eaabd345 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/GPOptionChoicePanel.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/GPOptionChoicePanel.java @@ -19,6 +19,7 @@ of the License, or (at your option) any later version. package net.sourceforge.ganttproject.gui.options; import biz.ganttproject.core.option.GPOptionGroup; +import javafx.beans.property.SimpleIntegerProperty; import net.sourceforge.ganttproject.gui.UIUtil; import javax.swing.*; @@ -42,6 +43,8 @@ public class GPOptionChoicePanel { private OptionsPageBuilder myOptionPageBuilder = new OptionsPageBuilder(); + public final SimpleIntegerProperty selectedIndexProperty = new SimpleIntegerProperty(-1); + public JComponent getComponent(Action[] choiceChangeActions, GPOptionGroup[] choiceOptions, int selectedGroupIndex) { JComponent[] choiceComponents = new JComponent[choiceOptions.length]; for (int i = 0; i < choiceChangeActions.length; i++) { @@ -108,6 +111,7 @@ private void setSelected(int selectedIndex) { mySelectedIndex = selectedIndex; newSelected.setSelected(true); setEnabledTree(myOptionComponents[mySelectedIndex], true); + selectedIndexProperty.set(selectedIndex); } private void setEnabledTree(JComponent root, boolean isEnabled) { 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 new file mode 100644 index 0000000000..f748e81075 --- /dev/null +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/projectwizard/WizardImplFx.kt @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2026 Dmitry Barashev, BarD Software s.r.o. + * + * This file is part of GanttProject, an open-source 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.gui.projectwizard + +import biz.ganttproject.FXUtil +import biz.ganttproject.app.DialogController +import biz.ganttproject.app.RootLocalizer +import biz.ganttproject.app.dialog +import biz.ganttproject.app.setSwingBackground +import biz.ganttproject.core.option.ObservableBoolean +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 +import kotlinx.coroutines.javafx.JavaFx +import kotlinx.coroutines.launch +import kotlinx.coroutines.swing.Swing +import kotlinx.coroutines.withContext +import net.sourceforge.ganttproject.action.CancelAction +import net.sourceforge.ganttproject.action.GPAction +import net.sourceforge.ganttproject.action.OkAction +import javax.swing.JComponent +import kotlin.coroutines.EmptyCoroutineContext + +/** + * Import/Export wizard model. + */ +open class WizardModel { + val i18n = RootLocalizer + val coroutineScope = CoroutineScope(EmptyCoroutineContext) + var title: String = "" + var dialogId: String = "" + var onOk: () -> Unit = {} + var canFinish: () -> Boolean = { true } + var hasNext: () -> Boolean = { currentPage < pages.size - 1 } + var currentPage = 0 + + val pages = mutableListOf() + val needsRefresh = ObservableBoolean("", false) + val pageCountProperty = SimpleIntegerProperty(0) + + fun addPage(page: WizardPage) { + pages.add(page) + pageCountProperty.set(pages.size) + } + + fun removePage(page: WizardPage) { + pages.remove(page) + pageCountProperty.set(pages.size) + } + + fun hasPrev(): Boolean { + return currentPage > 0 + } +} + +/** + * Shows a wizard dialog using the provided builder. + */ +fun showWizard(builder: WizardModel) { + dialog(builder.title, builder.dialogId) { ctrl -> + val ui = WizardUiFx(ctrl, builder) + ui.show(ctrl) + } +} + +/** + * Implements a wizard dialog UI using Java FX. + */ +private class WizardUiFx(private val ctrl: DialogController, private val model: WizardModel) { + private val coroutineScope = model.coroutineScope + private val pages = model.pages + private val i18n = RootLocalizer + private var nextButton: Button = Button() + private var backButton: Button = Button() + private var finishButton: Button = Button() + private val stackPane = StackPane().also { + it.styleClass.add("page-container") + it.styleClass.add("swing-background") + } + private val titleString = i18n.create("exportWizard.page.header") + + init { + backButton = ctrl.setupButton(GPAction.create("back") { + backPage() + }) { btn -> + btn.styleClass.addAll("btn-regular") + btn.addEventFilter(ActionEvent.ACTION) { + it.consume() + backPage() + } + }!! + + nextButton = ctrl.setupButton(GPAction.create("next") { + nextPage() + }) { btn -> + btn.styleClass.addAll("btn-regular", "secondary") + btn.addEventFilter(ActionEvent.ACTION) { + it.consume() + nextPage() + } + }!! + + finishButton = ctrl.setupButton(OkAction.create("ok") { + onOkPressed() + })!! + + // Cancel Button + ctrl.setupButton(CancelAction.create("cancel") { + onCancelPressed() + }) + + model.needsRefresh.addWatcher { evt -> + if (evt.newValue && evt.trigger != this) { + adjustButtonState() + model.needsRefresh.set(false, this) + } + } + + ctrl.resize() + } + fun show(ctrl: DialogController) { + ctrl.addStyleSheet("/biz/ganttproject/app/Dialog.css") + ctrl.addStyleSheet("/biz/ganttproject/impex/Exporter.css") + ctrl.addStyleClass("dlg", "dlg-export-wizard", "swing-background") + ctrl.setHeader(vbox { + addClasses("header") + addTitle(titleString) + }) + ctrl.setContent(stackPane) + ctrl.onShown = { + updatePage() + ctrl.setSwingBackground() + ctrl.resize() + } + } + + private fun nextPage() { + if (model.hasNext()) { + currentPage.setActive(false) + model.currentPage++ + updatePage() + } + } + + private fun backPage() { + if (model.hasPrev()) { + currentPage.setActive(false) + model.currentPage-- + updatePage() + } + } + + private fun updatePage() { + val page = currentPage + page.setActive(true) + + titleString.update(page.title) + //, i18n.formatText("step"), model.currentPage + 1, i18n.formatText("of"), pages.size + val swingNode = SwingNode() + coroutineScope.launch { + withContext(Dispatchers.Swing) { + swingNode.content = page.component as JComponent + } + withContext(Dispatchers.JavaFx) { + //borderPane.center = swingNode + FXUtil.transitionNode(stackPane, { + stackPane.children.clear() + stackPane.children.add(swingNode) + }, { + ctrl.resize() + adjustButtonState() + }) + } + } + } + + private fun adjustButtonState() { + backButton.isDisable = !model.hasPrev() + nextButton.isDisable = !model.hasNext() + finishButton.isDisable = !canFinish() + } + + private fun canFinish(): Boolean = model.canFinish() + + private fun onOkPressed() { + currentPage.setActive(false) + model.onOk() + } + + private fun onCancelPressed() { + currentPage.setActive(false) + } + + private val currentPage: WizardPage + get() = pages[model.currentPage] +} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/FileChooserPage.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/FileChooserPage.java deleted file mode 100644 index f7b8a1def5..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/FileChooserPage.java +++ /dev/null @@ -1,94 +0,0 @@ -/* -GanttProject is an opensource project management tool. -Copyright (C) 2005-2011 GanttProject team - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.importer; - -import java.io.File; - -import javax.swing.JFileChooser; -import javax.swing.filechooser.FileFilter; - -import org.osgi.service.prefs.Preferences; - -import com.google.common.base.Objects; - -import biz.ganttproject.core.option.GPOptionGroup; -import net.sourceforge.ganttproject.filter.ExtensionBasedFileFilter; -import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.language.GanttLanguage; -import net.sourceforge.ganttproject.wizard.AbstractFileChooserPage; -import net.sourceforge.ganttproject.wizard.WizardPage; - -/** - * @author bard - */ -class FileChooserPage extends AbstractFileChooserPage { - - private final Importer myImporter; - private File myFile; - - public FileChooserPage(UIFacade uiFacade, Importer importer, Preferences prefs) { - super(uiFacade, prefs, GanttLanguage.getInstance().getText("importerFileChooserPageTitle"), createFileFilter(importer), createOptions(importer), false); - myImporter = importer; - } - - - @Override - protected int getFileChooserSelectionMode() { - return JFileChooser.FILES_ONLY; - } - - private static FileFilter createFileFilter(Importer importer) { - return new ExtensionBasedFileFilter(importer.getFileNamePattern(), importer.getFileTypeDescription()); - } - - private static GPOptionGroup[] createOptions(Importer importer) { - return importer.getSecondaryOptions(); - } - - @Override - public String getTitle() { - return GanttLanguage.getInstance().getText("importerFileChooserPageTitle"); - } - - @Override - protected void setFile(File file) { - if (Objects.equal(file, myFile)) { - return; - } - myImporter.setFile(file); - if (file != null) { - getPreferences().put(AbstractFileChooserPage.PREF_SELECTED_FILE, file.getAbsolutePath()); - } - WizardPage importerPage = myImporter.getCustomPage(); - if (importerPage != null) { - getWizard().setNextPage(importerPage); - } - if (myImporter.isReady()) { - getWizard().setOkAction(new Runnable() { - @Override - public void run() { - myImporter.run(); - } - }); - } else { - getWizard().setOkAction(null); - } - myFile = file; - } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt new file mode 100644 index 0000000000..94a868cb99 --- /dev/null +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizard.kt @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2011-2026 Dmitry Barashev, BarD Software s.r.o. + * + * This file is part of GanttProject, an open-source 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.importer + +import biz.ganttproject.app.RootLocalizer +import biz.ganttproject.core.option.GPOptionGroup +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.Result +import com.github.michaelbull.result.andThen +import javafx.beans.property.SimpleObjectProperty +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 +import net.sourceforge.ganttproject.gui.projectwizard.WizardPage +import net.sourceforge.ganttproject.gui.projectwizard.showWizard +import net.sourceforge.ganttproject.plugins.PluginManager.* +import org.osgi.service.prefs.Preferences +import java.io.File +import javax.swing.filechooser.FileFilter + +/** + * Wizard for importing files into a Gantt project. + */ +class ImportFileWizard(uiFacade: UIFacade, project: IGanttProject, pluginPreferences: Preferences, + importers: List = getImporters()) { + private val wizardModel = ImporterWizardModel() + init { + importers.forEach { it.setContext(project, uiFacade, pluginPreferences) } + val filePage = ImportFileChooserPage(wizardModel, project, pluginPreferences, uiFacade) + filePage.selectedFileProperty.addListener { _, _, _ -> + wizardModel.needsRefresh.set(true, this) + } + wizardModel.addPage(ImporterChooserPage(importers, uiFacade, pluginPreferences, wizardModel)) + wizardModel.addPage(filePage) + wizardModel.customPageProperty.addListener { _, oldValue, newValue -> + if (oldValue == null && newValue != null) { + wizardModel.addPage(newValue) + } else if (oldValue != null && newValue == null) { + wizardModel.removePage(oldValue) + } + } + } + + fun show() { + showWizard(wizardModel) + } +} + +/** + * Model for the import wizard, managing importer and file selection. + */ +class ImporterWizardModel: WizardModel() { + // Selected importer. Updates a customPageProperty with the custom page of the importer, if any. + var importer: Importer? = null + set(value) { + field = value + customPageProperty.set(null) + value?.customPage?.let { customPageProperty.set(it) } + } + + // Selected file. + var file: File? = null + set(value) { + field = value + importer?.setFile(value) + } + + // Some importers, e.g. ICS importer, provide a custom page that is appended to the wizard. + val customPageProperty = SimpleObjectProperty(null) + + init { + canFinish = { importer != null && file != null } + hasNext = { when (currentPage) { + 0 -> importer != null + 1 -> customPageProperty.get() != null && file != null + else -> false + } } + onOk = { importer?.run() } + } +} + +private fun getImporters(): MutableList { + return getExtensions(Importer.EXTENSION_POINT_ID, Importer::class.java) +} + +/** + * Wizard page for choosing a file to import from. + */ +private class ImportFileChooserPage( + private val state: ImporterWizardModel, project: IGanttProject, prefs: Preferences, uiFacade: UIFacade) + : FileChooserPageBase(prefs, project.document, uiFacade, fileChooserTitle = "", + pageTitle = i18n.formatText("importerFileChooserPageTitle")) { + + val importer get() = state.importer + + init { + hasOverwriteOption = false + selectedFileProperty.addListener { value, file, newValue -> + state.file = newValue + } + } + + override fun createFileFilter(): FileFilter? = + importer?.let { + return ExtensionBasedFileFilter(it.getFileNamePattern(), it.getFileTypeDescription()) + } + + + override val optionGroups: List = emptyList() + + override fun validateFile(file: File?): Result { + return super.validateFile(file).andThen { file -> + if (file?.isDirectory ?: false) { + Err("It is a directory") + } else { + Ok(file) + } + } + } +} + +private val i18n = RootLocalizer diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizardImpl.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizardImpl.java deleted file mode 100644 index 941e72bf6e..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImportFileWizardImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -/* -GanttProject is an opensource project management tool. -Copyright (C) 2005-2011 GanttProject team - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.importer; - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.List; - -import net.sourceforge.ganttproject.GPLogger; -import net.sourceforge.ganttproject.GanttOptions; -import net.sourceforge.ganttproject.IGanttProject; -import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.gui.projectwizard.WizardImpl; -import net.sourceforge.ganttproject.gui.projectwizard.WizardPage; -import net.sourceforge.ganttproject.language.GanttLanguage; -import net.sourceforge.ganttproject.plugins.PluginManager; -import net.sourceforge.ganttproject.wizard.AbstractWizard; - -/** - * @author bard - */ -public class ImportFileWizardImpl extends AbstractWizard { - private static List ourImporters = getImporters(); - - private static GanttLanguage i18n = GanttLanguage.getInstance(); - - public ImportFileWizardImpl(UIFacade uiFacade, IGanttProject project, GanttOptions options) { - super(uiFacade, i18n.getText("importWizard.dialog.title"), - new ImporterChooserPage(ourImporters, uiFacade, options.getPluginPreferences().node("/instance/net.sourceforge.ganttproject/import"))); - for (Importer importer : ourImporters) { - importer.setContext(project, uiFacade, options.getPluginPreferences()); - } - } - - private static List getImporters() { - return PluginManager.getExtensions(Importer.EXTENSION_POINT_ID, Importer.class); - } - -// @Override -// protected void onOkPressed() { -// super.onOkPressed(); -// try { -// myState.getImporter().run(); -// } catch (Throwable e) { -// GPLogger.log(e); -// } -// } - -// class State { -// private Importer myImporter; -// -// private URL myUrl; -// -// public void setUrl(URL url) { -// if (url == null) { -// return; -// } -// myUrl = url; -// if ("file".equals(url.getProtocol())) { -// try { -// String path = URLDecoder.decode(url.getPath(), "utf-8"); -// myState.getImporter().setFile(new File(path)); -// } catch (UnsupportedEncodingException e) { -// GPLogger.log(e); -// } -// } else { -// GPLogger.logToLogger(new Exception(String.format("URL=%s is not a file", url.toString()))); -// } -// } -// -// public URL getUrl() { -// return myUrl; -// } -// -// Importer getImporter() { -// return myImporter; -// } -// -// void setImporter(Importer importer) { -// myImporter = importer; -// addImporterPages(importer); -// } -// } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/Importer.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/Importer.java index c3f8354ec4..8e9a4b84e7 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/Importer.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/Importer.java @@ -20,12 +20,12 @@ import java.io.File; +import net.sourceforge.ganttproject.gui.projectwizard.WizardPage; import org.osgi.service.prefs.Preferences; import biz.ganttproject.core.option.GPOptionGroup; import net.sourceforge.ganttproject.IGanttProject; import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.wizard.WizardPage; public interface Importer { String getID(); diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterBase.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterBase.java index 122ef45f29..dcbfe13cf5 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterBase.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterBase.java @@ -24,9 +24,9 @@ import net.sourceforge.ganttproject.IGanttProject; import net.sourceforge.ganttproject.gui.NotificationChannel; import net.sourceforge.ganttproject.gui.UIFacade; +import net.sourceforge.ganttproject.gui.projectwizard.WizardPage; import net.sourceforge.ganttproject.language.GanttLanguage; import net.sourceforge.ganttproject.util.collect.Pair; -import net.sourceforge.ganttproject.wizard.WizardPage; import org.osgi.service.prefs.Preferences; import java.io.File; @@ -46,10 +46,6 @@ public abstract class ImporterBase implements Importer { private Preferences myPrefs; private File myFile; -// protected ImporterBase() { -// myID = ""; -// } -// protected ImporterBase(String id) { myID = id; } diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterChooserPage.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterChooserPage.java index 8f43840ba6..f92bf5a220 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterChooserPage.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/importer/ImporterChooserPage.java @@ -18,7 +18,6 @@ */ package net.sourceforge.ganttproject.importer; -import java.awt.Component; import java.awt.event.ActionEvent; import java.util.List; @@ -26,29 +25,28 @@ import javax.swing.Action; import javax.swing.JComponent; +import org.jetbrains.annotations.NotNull; import org.osgi.service.prefs.Preferences; import biz.ganttproject.core.option.GPOptionGroup; import net.sourceforge.ganttproject.gui.UIFacade; import net.sourceforge.ganttproject.gui.options.GPOptionChoicePanel; import net.sourceforge.ganttproject.language.GanttLanguage; -import net.sourceforge.ganttproject.wizard.AbstractWizard; -import net.sourceforge.ganttproject.wizard.WizardPage; /** * @author bard */ -class ImporterChooserPage implements WizardPage { +class ImporterChooserPage implements net.sourceforge.ganttproject.gui.projectwizard.WizardPage { private final List myImporters; - private AbstractWizard myWizard; private final UIFacade myUiFacade; private final Preferences myPrefs; - private int mySelectedIndex; + private final ImporterWizardModel myWizardState; - ImporterChooserPage(List importers, UIFacade uiFacade, Preferences preferences) { + ImporterChooserPage(@NotNull List importers, UIFacade uiFacade, Preferences preferences, @NotNull ImporterWizardModel wizardState) { myImporters = importers; myUiFacade = uiFacade; myPrefs = preferences; + myWizardState = wizardState; } @Override @@ -66,29 +64,29 @@ public JComponent getComponent() { Action nextAction = new AbstractAction(importer.getFileTypeDescription()) { @Override public void actionPerformed(ActionEvent e) { - mySelectedIndex = index; - onSelectImporter(importer); + //onSelectImporter(importer); } }; choiceChangeActions[i] = nextAction; choiceOptions[i] = null; } GPOptionChoicePanel panel = new GPOptionChoicePanel(); + panel.selectedIndexProperty.addListener((observable, oldValue, newValue) -> + onSelectImporter(myImporters.get(newValue.intValue())) + ); return panel.getComponent(choiceChangeActions, choiceOptions, 0); } protected void onSelectImporter(Importer importer) { - assert myWizard != null : "It is a bug: importer chooser has not been initialized properly"; - WizardPage filePage = new FileChooserPage(myUiFacade, importer, myPrefs.node(importer.getID())); - myWizard.setNextPage(filePage); + myWizardState.setImporter(importer); } @Override - public void setActive(AbstractWizard wizard) { - myWizard = wizard; - if (wizard != null) { - onSelectImporter(myImporters.get(mySelectedIndex)); - } + public void setActive(boolean b) { +// myWizard = wizard; +// if (wizard != null) { +// onSelectImporter(myImporters.get(mySelectedIndex)); +// } } diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/AbstractFileChooserPage.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/AbstractFileChooserPage.java deleted file mode 100644 index 84d24df8d0..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/AbstractFileChooserPage.java +++ /dev/null @@ -1,408 +0,0 @@ -/* -GanttProject is an opensource project management tool. -Copyright (C) 2011 GanttProject team - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.wizard; - -import biz.ganttproject.core.option.DefaultBooleanOption; -import biz.ganttproject.core.option.GPOptionGroup; -import com.google.common.base.MoreObjects; -import net.sourceforge.ganttproject.gui.TextFieldAndFileChooserComponent; -import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.gui.UIUtil; -import net.sourceforge.ganttproject.gui.options.GPOptionChoicePanel; -import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder; -import net.sourceforge.ganttproject.language.GanttLanguage; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.osgi.service.prefs.Preferences; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.filechooser.FileFilter; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.text.MessageFormat; -import java.util.Timer; -import java.util.TimerTask; - -public abstract class AbstractFileChooserPage implements WizardPage { - public static final int FILE_SOURCE = 0; - public static final int URL_SOURCE = 1; - protected static final String PREF_SELECTED_FILE = "selected_file"; - private static final String PREF_SELECTED_URL = "selected_url"; - private static final FileFilter ACCEPT_ALL = new FileFilter() { - @Override - public String getDescription() { - return null; - } - @Override - public boolean accept(File f) { - return true; - } - }; - - private JPanel myComponent; - private TextFieldAndFileChooserComponent myChooser; - private JTextField myUrlField; - private final OptionsPageBuilder myOptionsBuilder; - private final JPanel mySecondaryOptionsComponent; - private int ourSelectedSource = AbstractFileChooserPage.FILE_SOURCE; - private final boolean isUrlChooserEnabled; - private final JLabel myFileLabel = new JLabel(" "); - private final JLabel myUrlLabel = new JLabel(" "); - private final Preferences myPreferences; - private final UIFacade myUiFacade; - private final FileFilter myFileFilter; - private final GPOptionGroup[] myOptions; - private final String myTitle; - private AbstractWizard myWizard; - - protected AbstractFileChooserPage(UIFacade uiFacade, Preferences prefs, String title, FileFilter fileFilter, GPOptionGroup[] options, boolean enableUrlChooser) { - myUiFacade = uiFacade; - myPreferences = prefs; - myTitle = title; - myFileFilter = MoreObjects.firstNonNull(fileFilter, ACCEPT_ALL); - myOptions = MoreObjects.firstNonNull(options, new GPOptionGroup[0]); - isUrlChooserEnabled = enableUrlChooser; - myOptionsBuilder = new OptionsPageBuilder(); - mySecondaryOptionsComponent = new JPanel(new BorderLayout()); - mySecondaryOptionsComponent.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); - } - - protected int getFileChooserSelectionMode() { - return JFileChooser.FILES_AND_DIRECTORIES; - } - - @Override - public JComponent getComponent() { - myComponent = new JPanel(new BorderLayout()); - myChooser = new TextFieldAndFileChooserComponent(myUiFacade, myTitle) { - @Override - protected void onFileChosen(File file) { - AbstractFileChooserPage.this.onSelectedFileChange(file); - } - }; - myChooser.setFileSelectionMode(getFileChooserSelectionMode()); - JComponent contentPanel = new JPanel(new BorderLayout()); - if (!isUrlChooserEnabled) { - contentPanel.add(myChooser, BorderLayout.NORTH); - } else { - final UrlFetcher urlFetcher = new UrlFetcher() { - @Override - protected void onFetchComplete(File file) { - super.onFetchComplete(file); - onSelectedFileChange(file); - } - }; - myUrlField = new JTextField(); - Box urlBox = Box.createVerticalBox(); - urlBox.add(myUrlField); - urlBox.add(myUrlLabel); - myUrlField.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void removeUpdate(DocumentEvent e) { - onChange(); - } - - @Override - public void insertUpdate(DocumentEvent e) { - onChange(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - onChange(); - } - - private void onChange() { - urlFetcher.setUrl(getSelectedUrl()); - } - }); - - Box fileBox = Box.createVerticalBox(); - fileBox.add(myChooser); - fileBox.add(myFileLabel); - - Action fileSourceAction = new AbstractAction(GanttLanguage.getInstance().getText("file")) { - @Override - public void actionPerformed(ActionEvent e) { - ourSelectedSource = AbstractFileChooserPage.FILE_SOURCE; - myChooser.tryFile(); - } - }; - Action urlSourceAction = new AbstractAction(GanttLanguage.getInstance().getText("url")) { - @Override - public void actionPerformed(ActionEvent e) { - ourSelectedSource = AbstractFileChooserPage.URL_SOURCE; - urlFetcher.setStatusLabel(myUrlLabel); - urlFetcher.setUrl(getSelectedUrl()); - } - }; - Action[] importSourceActions = new Action[] { fileSourceAction, urlSourceAction }; - JComponent[] importSourceComponents = new JComponent[] { fileBox, urlBox }; - GPOptionChoicePanel sourceChoicePanel = new GPOptionChoicePanel(); - - Box sourceBox = Box.createVerticalBox(); - sourceBox.add(sourceChoicePanel.getComponent(importSourceActions, importSourceComponents, ourSelectedSource)); - sourceBox.add(Box.createVerticalStrut(5)); - sourceBox.add(urlFetcher.getComponent()); - contentPanel.add(sourceBox, BorderLayout.NORTH); - } - contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER); - myComponent.add(contentPanel, BorderLayout.NORTH); - return myComponent; - } - - protected void loadPreferences() { - String oldFile = myPreferences.get(AbstractFileChooserPage.PREF_SELECTED_FILE, null); - if (oldFile != null) { - // Use the previously used path with the current filename for the default - // name - // The implementing classes can modify the file extension when desired - File f = new File(oldFile); - onSelectedFileChange(f); - myChooser.setFile(f); - } - if (myUrlField != null && myPreferences.get(AbstractFileChooserPage.PREF_SELECTED_URL, null) != null) { - myUrlField.setText(myPreferences.get(AbstractFileChooserPage.PREF_SELECTED_URL, null)); - } - } - - @Override - public void setActive(AbstractWizard wizard) { - GPOptionGroup[] optionGroups = getOptionGroups(); - myWizard = wizard; - if (wizard == null) { - // Means that user switched to either previous or next page - if (myChooser.getFile() != null) { - myPreferences.put(AbstractFileChooserPage.PREF_SELECTED_FILE, myChooser.getFile().getAbsolutePath()); - } - if (myUrlField != null) { - myPreferences.put(AbstractFileChooserPage.PREF_SELECTED_URL, myUrlField.getText()); - } - } else { - if (mySecondaryOptionsComponent != null) { - mySecondaryOptionsComponent.removeAll(); - } - mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH); - myChooser.setFileFilter(myFileFilter); - loadPreferences(); - wizard.getDialog().layout(); - } - } - - protected Component createSecondaryOptionsPanel() { - return myOptionsBuilder.buildPlanePage(getOptionGroups()); - } - - protected final Preferences getPreferences() { - return myPreferences; - } - - protected final TextFieldAndFileChooserComponent getChooser() { - return myChooser; - } - - protected AbstractWizard getWizard() { - return myWizard; - } - - private URL getSelectedUrl() { - try { - switch (ourSelectedSource) { - case FILE_SOURCE: - return myChooser.getSelectedURL(); - case URL_SOURCE: - return new URL(myUrlField.getText()); - default: - assert false : "Should not be here"; - return null; - } - } catch (MalformedURLException e) { - reportMalformedUrl(e); - return null; - } - } - - private void reportMalformedUrl(Exception e) { - } - - protected void setOptionsEnabled(boolean enabled) { - if (mySecondaryOptionsComponent != null) { - setEnabledTree(mySecondaryOptionsComponent, enabled); - } - } - - private void setEnabledTree(JComponent root, boolean isEnabled) { - UIUtil.setEnabledTree(root, isEnabled); - } - - private GPOptionGroup[] getOptionGroups() { - return myOptions; - } - - protected abstract void setFile(File file); - - protected void onSelectedFileChange(File file) { - IStatus status = getFileStatus(file); - if (status.isOK()) { - setFile(file); - } else { - setFile(null); - } - showStatus(myFileLabel, status); - } - - private static IStatus getFileStatus(File file) { - if (file == null) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File does not exist", null); - } - if (!file.exists()) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File does not exist", null); - } - if (!file.canRead()) { - return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File read error", null); - } - return Status.OK_STATUS; - } - - private static void showStatus(JLabel label, IStatus status) { - label.setOpaque(true); - if (status.isOK()) { - label.setForeground(Color.BLACK); - } else { - label.setForeground(Color.RED); - } - label.setText(status.getMessage()); - } - - class UrlFetcher { - private final DefaultBooleanOption myProgressOption = new DefaultBooleanOption(""); - private JLabel myStatusLabel; - private final Timer myTimer = new Timer(); - private boolean isFetching; - private URL myUrl; - private File myFetchedFile = null; - - public UrlFetcher() { - } - - public void setStatusLabel(JLabel label) { - myStatusLabel = label; - } - - Component getComponent() { - OptionsPageBuilder builder = new OptionsPageBuilder(); - return builder.createWaitIndicatorComponent(myProgressOption); - } - - void setUrl(final URL url) { - synchronized (myTimer) { - myUrl = url; - if (isFetching) { - return; - } - reschedule(); - } - } - - private void fetch() { - myProgressOption.lock(); - myProgressOption.toggle(); - myProgressOption.commit(); - try { - URLConnection connection = myUrl.openConnection(); - connection.connect(); - File tempFile = File.createTempFile("gp-import-", ""); - tempFile.deleteOnExit(); - InputStream from = myUrl.openStream(); - try { - OutputStream to = new FileOutputStream(tempFile); - try { - byte[] buf = new byte[1024]; - while (true) { - int r = from.read(buf); - if (r == -1) { - break; - } - to.write(buf, 0, r); - } - myFetchedFile = tempFile; - setStatus(new Status(IStatus.OK, "foo", IStatus.OK, MessageFormat.format("Successfully fetched from {0}", - new Object[] { myUrl }), null)); - } finally { - to.flush(); - to.close(); - } - } finally { - from.close(); - } - } catch (IOException e) { - setStatus(new Status(IStatus.ERROR, "foo", IStatus.ERROR, MessageFormat.format("Failed to fetch from {0}\n{1}", - new Object[] { myUrl, e.getMessage() }), e)); - } finally { - isFetching = false; - myProgressOption.lock(); - myProgressOption.toggle(); - myProgressOption.commit(); - onFetchComplete(myFetchedFile); - } - } - - private void setStatus(IStatus status) { - AbstractFileChooserPage.showStatus(myStatusLabel, status); - } - - protected void onFetchComplete(File file) { - onSelectedFileChange(file); - } - - private void reschedule() { - if (myUrl == null || myUrl.getHost() == null || myUrl.getHost().length() == 0) { - onFetchComplete(null); - return; - } - myFetchedFile = null; - onFetchComplete(null); - isFetching = true; - myTimer.schedule(new TimerTask() { - final URL myUrlAtStart = myUrl; - - @Override - public void run() { - synchronized (myTimer) { - if (!myUrlAtStart.equals(myUrl)) { - reschedule(); - } else { - fetch(); - } - } - } - }, 3000); - } - } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/AbstractWizard.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/AbstractWizard.java deleted file mode 100644 index c1f71d3d23..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/AbstractWizard.java +++ /dev/null @@ -1,221 +0,0 @@ -/* -Copyright (C) 2013 BarD Software s.r.o - -This program 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. - -This program 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 this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package net.sourceforge.ganttproject.wizard; - -import com.google.common.collect.Maps; -import net.sourceforge.ganttproject.action.CancelAction; -import net.sourceforge.ganttproject.action.GPAction; -import net.sourceforge.ganttproject.action.OkAction; -import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.gui.UIFacade.Centering; -import net.sourceforge.ganttproject.gui.UIFacade.Dialog; -import net.sourceforge.ganttproject.gui.options.TopPanel; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * A wizard abstraction capable of managing wizard pages and showing them in the UI - * according to the user actions. - * - * @author dbarashev (Dmitry Barashev) - */ -public class AbstractWizard { - private final ArrayList myPages = new ArrayList(); - - private final Map myTitle2component = Maps.newHashMap(); - - private int myCurrentPage; - - private final JPanel myPagesContainer; - - private final CardLayout myCardLayout; - - private final AbstractAction myNextAction; - - private final AbstractAction myBackAction; - - private final AbstractAction myOkAction; - - private final AbstractAction myCancelAction; - - private final UIFacade myUIFacade; - - private final String myTitle; - - private final Dialog myDialog; - - private Runnable myOkRunnable; - - public AbstractWizard(UIFacade uiFacade, String title, WizardPage firstPage) { - myUIFacade = uiFacade; - myTitle = title; - myCardLayout = new CardLayout(); - myPagesContainer = new JPanel(myCardLayout); - myNextAction = new GPAction("next") { - @Override - public void actionPerformed(ActionEvent e) { - AbstractWizard.this.nextPage(); - } - }; - myBackAction = new GPAction("back") { - @Override - public void actionPerformed(ActionEvent e) { - AbstractWizard.this.backPage(); - } - }; - myOkAction = new OkAction() { - @Override - public void actionPerformed(ActionEvent e) { - onOkPressed(); - } - }; - myCancelAction = new CancelAction(); - myPagesContainer.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - myDialog = myUIFacade.createDialog(myPagesContainer, new Action[] { myBackAction, myNextAction, myOkAction, - myCancelAction }, myTitle, null); - addPageComponent(firstPage); - myPages.add(firstPage); - myDialog.layout(); - myDialog.center(Centering.WINDOW); - adjustButtonState(); - } - - private void nextPage() { - assert myCurrentPage + 1 < myPages.size() : "It is a bug: we have no next page while Next button is enabled and has been pressed"; - getCurrentPage().setActive(null); - WizardPage nextPage = myPages.get(myCurrentPage + 1); - if (myTitle2component.get(nextPage.getTitle()) == null) { - addPageComponent(nextPage); - } - myCurrentPage++; - nextPage.setActive(this); - myCardLayout.show(myPagesContainer, nextPage.getTitle()); - myDialog.center(Centering.WINDOW); - myDialog.layout(); - adjustButtonState(); - } - - private void addPageComponent(WizardPage page) { - if (myTitle2component.get(page.getTitle()) == null) { - JComponent c = wrapePageComponent(page.getTitle(), page.getComponent()); - myPagesContainer.add(c, page.getTitle()); - myTitle2component.put(page.getTitle(), c); - } - } - - private JComponent wrapePageComponent(String title, JComponent c) { - JPanel pagePanel = new JPanel(new BorderLayout()); - JComponent titlePanel = TopPanel.create(title, null); - pagePanel.add(titlePanel, BorderLayout.NORTH); - c.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); - pagePanel.add(c, BorderLayout.CENTER); - return pagePanel; - } - - private void backPage() { - if (myCurrentPage > 0) { - getCurrentPage().setActive(null); - myCurrentPage--; - myCardLayout.show(myPagesContainer, getCurrentPage().getTitle()); - getCurrentPage().setActive(this); - } - //myDialog.center(Centering.WINDOW); - adjustButtonState(); - } - - public void show() { - myCardLayout.first(myPagesContainer); - getCurrentPage().setActive(this); - adjustButtonState(); - myDialog.show(); - myDialog.center(Centering.SCREEN); - } - - private void adjustButtonState() { - myBackAction.setEnabled(myCurrentPage > 0); - myNextAction.setEnabled(myCurrentPage < myPages.size() - 1); - myOkAction.setEnabled(canFinish()); - } - - protected void onOkPressed() { - myOkRunnable.run(); - } - - protected boolean canFinish() { - return myOkRunnable != null; - } - - private boolean isExistingNextPage(WizardPage page) { - if (page == null) { - return false; - } - int idxPage = myPages.indexOf(page); - return (idxPage != -1 && myCurrentPage == idxPage - 1); - } - - /** - * Active wizard page can call this method to set a next page. - * - * @param page next page - */ - public void setNextPage(WizardPage page) { - boolean isExisting = isExistingNextPage(page); - if (!isExisting) { - List tail = myPages.subList(myCurrentPage + 1, myPages.size()); - for (WizardPage tailPage : tail) { - JComponent component = myTitle2component.remove(tailPage.getTitle()); - if (component != null) { - myPagesContainer.remove(component); - } - } - tail.clear(); - if (page != null) { - myPages.add(page); - } - } - adjustButtonState(); - } - - /** - * Wizard pages or specific wizard implementations can call this method to set an - * action to be called when user clicks OK. This makes OK button enabled. - * - * @param action action to be called on OK - */ - public void setOkAction(Runnable action) { - myOkRunnable = action; - adjustButtonState(); - } - - private WizardPage getCurrentPage() { - return myPages.get(myCurrentPage); - } - - public UIFacade getUIFacade() { - return myUIFacade; - } - - public Dialog getDialog() { - return myDialog; - } -} diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/WizardPage.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/WizardPage.java deleted file mode 100644 index fc1927e2fa..0000000000 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/wizard/WizardPage.java +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2013 BarD Software s.r.o - -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.wizard; - -import javax.swing.JComponent; - -/** - * Abstraction of the wizard page. Page lifecycle is the following: - * -- page is instantiated - * -- when user reaches page in the wizard, its getComponent method is called, the returned component - * is inserted into the wizard UI and page's setActive method is called. Wizard instance is passed to setActive, - * and if page realizes that it has a next page, it should instantiate it and call wizard's {@link AbstractWizard.setNextPage()} method - * -- when user leaves the page, setActive() is called with null argument. It is not necessary to do anything, but page - * can save its data at this moment - * - * @author dbarashev - */ -public interface WizardPage { - /** @return the title of the page */ - String getTitle(); - - /** @return the Component that makes the page */ - JComponent getComponent(); - - /** - * Indicates that this page is now active (visible, that is) in the wizard or just've been made inactive (user switched - * to some other page) - * - * @param wizard wizard instance when page becomes active or null when it deactivates - */ - void setActive(AbstractWizard wizard); -} diff --git a/ganttproject/src/main/sass/biz/ganttproject/app/dialogs.scss b/ganttproject/src/main/sass/biz/ganttproject/app/dialogs.scss index 508e2604a9..4c2912288b 100644 --- a/ganttproject/src/main/sass/biz/ganttproject/app/dialogs.scss +++ b/ganttproject/src/main/sass/biz/ganttproject/app/dialogs.scss @@ -46,6 +46,9 @@ $alert-foreground: #721c24; -fx-padding: 2*$default-vertical-padding 1em $default-vertical-padding 1em; -fx-background-color: whitesmoke; //-fx-border-color: yellow; + .button-bar { + -fx-background-color: transparent; + } } .dialog-alert { diff --git a/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss b/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss index 2a33f5fd09..bc637aecfc 100644 --- a/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss +++ b/ganttproject/src/main/sass/biz/ganttproject/impex/Exporter.scss @@ -49,6 +49,21 @@ -fx-accent: $gp-error } } +} +.dlg-export-wizard { + .header { + @include dialog-header($gp-orange); + } + .content-pane { + -fx-pref-height: 600; + -fx-pref-width: 600; + @include dialog-content-padding(); + -fx-border-width: 0; + //-fx-border-color: red; + } + .button-pane { + @include dialog-button-pane(); + } }