diff --git a/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt b/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt index 2126773315..c338189b86 100644 --- a/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt +++ b/ganttproject/src/main/java/biz/ganttproject/FXUtil.kt @@ -46,6 +46,9 @@ import net.sourceforge.ganttproject.gui.ActionUtil import javax.swing.SwingUtilities import javax.swing.UIManager +interface FxUiComponent { + fun buildNode(): Node +} /** * @author dbarashev@bardsoftware.com */ diff --git a/ganttproject/src/main/java/biz/ganttproject/app/AppearanceManager.kt b/ganttproject/src/main/java/biz/ganttproject/app/AppearanceManager.kt index 1353a210e5..488cfd40b0 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/AppearanceManager.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/AppearanceManager.kt @@ -18,6 +18,7 @@ */ package biz.ganttproject.app +import biz.ganttproject.FXUtil import biz.ganttproject.core.option.ChangeValueEvent import biz.ganttproject.core.option.DefaultFontOption import biz.ganttproject.core.option.FontSpec @@ -40,10 +41,12 @@ class AppearanceManager(private val appFontOption: DefaultFontOption) { reloadCustomCss(c.list) } appFontOption.addChangeValueListener { _: ChangeValueEvent -> - setFont(appFontOption.getValue()) - reloadCustomCss(Window.getWindows()) + FXUtil.runLater { + setFont(appFontOption.value) + reloadCustomCss(Window.getWindows()) + } } - setFont(appFontOption.getValue()) + setFont(appFontOption.value) } fun setFont(font: FontSpec) { diff --git a/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt b/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt index 8230d66c95..60d741d09b 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/Dialog.kt @@ -80,7 +80,6 @@ fun dialogFxBuild(owner: Window? = null, id: String? = null, contentBuilder: (Di owner?.let(::initOwner) initModality(Modality.APPLICATION_MODAL) - DialogControllerFx(dialogPane, this).let { dialogBuildApi -> dialogPane.styleClass.addAll("dlg") dialogPane.stylesheets.addAll(DIALOG_STYLESHEET) @@ -607,6 +606,7 @@ class DialogControllerPane(private val root: BorderPane) : DialogController { } override fun removeButtonBar() { + root.bottom = null } override fun toggleProgress(shown: Boolean): () -> Unit { diff --git a/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt b/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt index 155b9fdee6..5054de1c88 100644 --- a/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt +++ b/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt @@ -221,7 +221,7 @@ internal class CustomPropertyEditor( selectedItemProperty: ObservableProperty, private val btnDeleteController: BtnController, escCloseEnabled: BooleanProperty, -) : ItemEditorPane(model.allOptions, selectedItemProperty, dialogModel, ourEditorLocalizer) { +) : ItemEditorPaneImpl(model.allOptions, selectedItemProperty, dialogModel, ourEditorLocalizer) { init { escCloseEnabled.bind(propertySheet.isEscCloseEnabled) diff --git a/ganttproject/src/main/java/biz/ganttproject/ganttview/FilterDialog.kt b/ganttproject/src/main/java/biz/ganttproject/ganttview/FilterDialog.kt index 32caf17e4c..763ec98ac9 100644 --- a/ganttproject/src/main/java/biz/ganttproject/ganttview/FilterDialog.kt +++ b/ganttproject/src/main/java/biz/ganttproject/ganttview/FilterDialog.kt @@ -103,7 +103,7 @@ internal class FilterEditor( editItem: ObservableObject, model: ItemListDialogModel ) - : ItemEditorPane( + : ItemEditorPaneImpl( editorModel.fields, editItem, model, i18n ) { override fun loadData(item: TaskFilter?) { diff --git a/ganttproject/src/main/java/biz/ganttproject/ganttview/SharedUiComponents.kt b/ganttproject/src/main/java/biz/ganttproject/ganttview/SharedUiComponents.kt index 5903fb4d20..b4ad2e9334 100644 --- a/ganttproject/src/main/java/biz/ganttproject/ganttview/SharedUiComponents.kt +++ b/ganttproject/src/main/java/biz/ganttproject/ganttview/SharedUiComponents.kt @@ -21,9 +21,6 @@ package biz.ganttproject.ganttview import biz.ganttproject.app.DialogController import biz.ganttproject.app.Localizer import biz.ganttproject.app.PropertySheetBuilder -import biz.ganttproject.app.RootLocalizer -import biz.ganttproject.core.option.GPObservable -import biz.ganttproject.core.option.ObservableBoolean import biz.ganttproject.core.option.ObservableProperty import biz.ganttproject.lib.fx.VBoxBuilder import biz.ganttproject.lib.fx.createToggleSwitch @@ -52,13 +49,14 @@ import java.util.WeakHashMap /** * This is an object that is rendered in a list view. */ -data class ShowHideListItem(val text: ()->String, val isVisible: ()->Boolean, val toggleVisible: ()->Unit) +data class ShowHideListItem(val text: ()->String, val isVisible: ()->Boolean, val toggleVisible: ()->Unit, val styleClass: String = "", val hasVisibilityToggle: Boolean = true) private val ourCells = WeakHashMap, Boolean>() /** * A list cell is a UI component that renders items in a list view. It adds a show/hide icon to the item title. */ internal class ShowHideListCell(private val converter: (T)-> ShowHideListItem) : ListCell() { + private var initialStyles: List private val iconVisible = MaterialIconView(MaterialIcon.VISIBILITY) private val iconHidden = MaterialIconView(MaterialIcon.VISIBILITY_OFF) private val iconPane = StackPane().also { @@ -70,16 +68,21 @@ internal class ShowHideListCell(private val converter: (T)-> ShowHideListItem val theItem by lazy { converter(item) } init { - styleClass.add("column-item-cell") + initialStyles = mutableListOf().also { it.addAll(styleClass) } alignment = Pos.CENTER_LEFT ourCells.put(this, true) } override fun updateItem(item: T?, empty: Boolean) { super.updateItem(item, empty) + styleClass.clear() + styleClass.addAll(initialStyles) + styleClass.add("column-item-cell") + //font = applicationFont.value if (item == null || empty) { text = "" graphic = null + styleClass.add("column-item-cell-empty") return } updateTheItem(converter(item)) @@ -93,15 +96,20 @@ internal class ShowHideListCell(private val converter: (T)-> ShowHideListItem if (graphic == null) { graphic = iconPane } - if (item.isVisible()) { - styleClass.add("is-visible") - styleClass.remove("is-hidden") - iconPane.children.setAll(iconVisible) - } else { - if (!styleClass.contains("is-hidden")) { - styleClass.remove("is-visible") - styleClass.add("is-hidden") - iconPane.children.setAll(iconHidden) + if (item.styleClass.isNotBlank()) { + styleClass.add(item.styleClass) + } + if (item.hasVisibilityToggle) { + if (item.isVisible()) { + styleClass.add("is-visible") + styleClass.remove("is-hidden") + iconPane.children.setAll(iconVisible) + } else { + if (!styleClass.contains("is-hidden")) { + styleClass.remove("is-visible") + styleClass.add("is-hidden") + iconPane.children.setAll(iconHidden) + } } } } @@ -113,21 +121,25 @@ internal class ShowHideListCell(private val converter: (T)-> ShowHideListItem } } +interface ItemEditorPane { + val node: Node + fun focus() +} /** * UI for editing properties of the selected list item. */ -internal open class ItemEditorPane>( +internal open class ItemEditorPaneImpl>( // Fields that need to be shown in the UI. fields: List>, protected val editItem: ObservableProperty, // The whole dialog model. private val dialogModel: ItemListDialogModel, // i18n - localizer: Localizer) { + localizer: Localizer): ItemEditorPane { private var isEditIgnored = false - val node: Node by lazy { + override val node: Node by lazy { vbox { addClasses("property-sheet-box") add(visibilityTogglePane) @@ -201,7 +213,7 @@ internal open class ItemEditorPane>( dialogModel.requireRefresh.set(true) } - internal fun focus() { + override fun focus() { propertySheet.requestFocus() onEdit() } @@ -239,7 +251,7 @@ data class BtnController( */ internal class ItemListDialogModel>( private val listItems: ObservableList, - private val newItemFactory: ()->T, + private val newItemFactory: ()->T?, private val i18n: Localizer, ) { val btnAddController = BtnController(onAction = this::onAddColumn) @@ -248,24 +260,25 @@ internal class ItemListDialogModel>( internal var selection: ()-> Collection = { emptyList() } val requireRefresh = SimpleBooleanProperty(false) - fun onAddColumn(): T { - val item = newItemFactory() - val title = i18n.create("addItem").also { - it.update("") - } - var count = 1 - while (listItems.any { it.title == title.value.trim() }) { - val prevValue = title.value - title.update(count.toString()) - if (prevValue == title.value) { - break + fun onAddColumn(): T? = + newItemFactory()?.let { item -> + val title = i18n.create("addItem").also { + it.update("") + } + var count = 1 + while (listItems.any { it.title == title.value.trim() }) { + val prevValue = title.value + title.update(count.toString()) + if (prevValue == title.value) { + break + } + count++ } - count++ + item.title = title.value.trim() + listItems.add(item) + item } - item.title = title.value.trim() - listItems.add(item) - return item - } + fun onDeleteColumn() { listItems.removeAll(selection()) @@ -285,12 +298,18 @@ internal class ItemListDialogPane>( // The dialog model. private val dialogModel: ItemListDialogModel, // The editor pane UI. - val editor: ItemEditorPane, + val editor: ItemEditorPane, // i18n. private val localizer: Localizer) { internal val listView: ListView = ListView() + var isAddRemoveEnabled = true + var isHeaderEnabled = true + var contentNode = HBox().also { + it.children.addAll(listView, editor.node) + HBox.setHgrow(editor.node, Priority.ALWAYS) + } init { listView.apply { this@ItemListDialogPane.dialogModel.selection = { selectionModel.selectedItems } @@ -317,46 +336,45 @@ internal class ItemListDialogPane>( fun build(dlg: DialogController) { dlg.addStyleClass("dlg-list-view-editor") dlg.addStyleSheet("/biz/ganttproject/ganttview/ListViewEditorDialog.css") - dlg.setHeader( - VBoxBuilder("header").apply { - addTitle(localizer.create("title")).also { hbox -> - hbox.alignment = Pos.CENTER_LEFT - hbox.isFillHeight = true - } - }.vbox - ) - - dlg.setContent(HBox().also { - it.children.addAll(listView, editor.node) - HBox.setHgrow(editor.node, Priority.ALWAYS) - }) + if (isHeaderEnabled) { + dlg.setHeader( + VBoxBuilder("header").apply { + addTitle(localizer.create("title")).also { hbox -> + hbox.alignment = Pos.CENTER_LEFT + hbox.isFillHeight = true + } + }.vbox + ) + } + dlg.setContent(contentNode) - dlg.setupButton(ButtonType(localizer.formatText("add"))) { btn -> - btn.text = localizer.formatText("add") - ButtonBar.setButtonData(btn, ButtonBar.ButtonData.HELP) - btn.disableProperty().bind(dialogModel.btnAddController.isDisabled) - btn.styleClass.addAll("btn-attention", "secondary") - btn.addEventFilter(ActionEvent.ACTION) { - it.consume() - dialogModel.btnAddController.onAction().also { item -> - listView.scrollTo(item) - listView.selectionModel.select(item) - editor.focus() + if (isAddRemoveEnabled) { + dlg.setupButton(ButtonType(localizer.formatText("add"))) { btn -> + btn.text = localizer.formatText("add") + ButtonBar.setButtonData(btn, ButtonBar.ButtonData.HELP) + btn.disableProperty().bind(dialogModel.btnAddController.isDisabled) + btn.styleClass.addAll("btn-attention", "secondary") + btn.addEventFilter(ActionEvent.ACTION) { + it.consume() + dialogModel.btnAddController.onAction().also { item -> + listView.scrollTo(item) + listView.selectionModel.select(item) + editor.focus() + } } } - } - dlg.setupButton(ButtonType(localizer.formatText("delete"))) { btn -> - btn.text = localizer.formatText("delete") - ButtonBar.setButtonData(btn, ButtonBar.ButtonData.HELP_2) - btn.disableProperty().bind(dialogModel.btnDeleteController.isDisabled) - btn.addEventFilter(ActionEvent.ACTION) { - it.consume() - dialogModel.btnDeleteController.onAction() + dlg.setupButton(ButtonType(localizer.formatText("delete"))) { btn -> + btn.text = localizer.formatText("delete") + ButtonBar.setButtonData(btn, ButtonBar.ButtonData.HELP_2) + btn.disableProperty().bind(dialogModel.btnDeleteController.isDisabled) + btn.addEventFilter(ActionEvent.ACTION) { + it.consume() + dialogModel.btnDeleteController.onAction() + } + btn.styleClass.addAll("btn-regular", "secondary") } - btn.styleClass.addAll("btn-regular", "secondary") } - dlg.setupButton(ButtonType.APPLY) { btn -> btn.text = localizer.formatText("apply") btn.styleClass.add("btn-attention") diff --git a/ganttproject/src/main/java/biz/ganttproject/platform/PlatformOptionPage.kt b/ganttproject/src/main/java/biz/ganttproject/platform/PlatformOptionPage.kt index 0cd5bd384f..54af6dbc6e 100644 --- a/ganttproject/src/main/java/biz/ganttproject/platform/PlatformOptionPage.kt +++ b/ganttproject/src/main/java/biz/ganttproject/platform/PlatformOptionPage.kt @@ -18,14 +18,19 @@ along with GanttProject. If not, see . */ package biz.ganttproject.platform +import biz.ganttproject.FXUtil +import biz.ganttproject.FxUiComponent import biz.ganttproject.app.DIALOG_STYLESHEET import biz.ganttproject.app.DialogControllerPane import biz.ganttproject.core.option.GPOptionGroup import com.bardsoftware.eclipsito.update.UpdateMetadata import javafx.application.Platform import javafx.embed.swing.JFXPanel +import javafx.scene.Node +import javafx.scene.Parent import javafx.scene.Scene import javafx.scene.layout.BorderPane +import javafx.scene.layout.StackPane import net.sourceforge.ganttproject.GPLogger import net.sourceforge.ganttproject.gui.options.OptionPageProviderBase import java.awt.BorderLayout @@ -36,9 +41,30 @@ import org.eclipse.core.runtime.Platform as Eclipsito /** * @author dbarashev@bardsoftware.com */ -class PlatformOptionPageProvider : OptionPageProviderBase("platform") { +class PlatformOptionPageProvider : OptionPageProviderBase("platform"), FxUiComponent { private var onSetActive: (() -> Unit)? = null + override fun buildNode(): Node = StackPane().also {stackPane -> + Eclipsito.getUpdater().getUpdateMetadata(UpdateOptions.updateUrl.value).thenAccept { updateMetadata -> + onSetActive = { + FXUtil.runLater { + stackPane.children.add(createSceneContent(updateMetadata, null)) + } + } + if (isActive) { + // We need to trigger onSetActive if we are already in the active state. + isActive = true + } + + }.exceptionally { ex -> + GPLogger.logToLogger(ex) + FXUtil.runLater { + stackPane.children.add(createSceneContent(emptyList(), ex)) + } + null + } + } + override fun getOptionGroups(): Array { return arrayOf() } @@ -75,6 +101,10 @@ class PlatformOptionPageProvider : OptionPageProviderBase("platform") { } private fun createScene(updateMetadata: List, ex: Throwable?): Scene { + return Scene(createSceneContent(updateMetadata, ex)) + } + + private fun createSceneContent(updateMetadata: List, ex: Throwable?): Parent { val runningVersion = Eclipsito.getUpdater().installedUpdateVersions.maxOrNull() ?: "2900" val runningUpdateMetadata = UpdateMetadata( runningVersion, @@ -92,7 +122,7 @@ class PlatformOptionPageProvider : OptionPageProviderBase("platform") { val updateModel = UpdateDialogModel(filteredUpdates, filteredUpdates, restarter = { uiFacade.quitApplication(false) }) updatesAvailableDialog(updateModel, dialogBuildApi) } - return Scene(group) + return group } } diff --git a/ganttproject/src/main/java/biz/ganttproject/platform/Update.kt b/ganttproject/src/main/java/biz/ganttproject/platform/Update.kt index ad229c956f..75ba3815ae 100644 --- a/ganttproject/src/main/java/biz/ganttproject/platform/Update.kt +++ b/ganttproject/src/main/java/biz/ganttproject/platform/Update.kt @@ -85,7 +85,7 @@ internal class UpdateDialog(private val model: UpdateDialogModel) { // Progress indicator private val installFromZipUi by lazy { UpdateFromZip(ourLocalizer).also { - model.installFromZip = it::installUpdate + model.installFromZip = { it.installUpdate() } } } private val installFromChannelUi by lazy { @@ -127,30 +127,33 @@ internal class UpdateDialog(private val model: UpdateDialogModel) { ) }) - dialogApi.setupButton(ButtonType.APPLY) { btn -> - ButtonBar.setButtonUniformSize(btn, false) - btn.styleClass.add("btn-attention") - btn.maxWidth = Double.MAX_VALUE - model.setupApplyButton(btn) - } - - if (!isFromSettings) { - // If we show this dialog on start-up, we allow for skipping the update and add the appropriate button. - // This button will also behave like "close" button if we install the update. - dialogApi.setupButton(ButtonType.CLOSE) { btn -> + if (model.state == ApplyAction.UP_TO_DATE) { + dialogApi.removeButtonBar() + } else { + dialogApi.setupButton(ButtonType.APPLY) { btn -> ButtonBar.setButtonUniformSize(btn, false) + btn.styleClass.add("btn-attention") btn.maxWidth = Double.MAX_VALUE - btn.styleClass.add("btn") - model.setupCloseButton(btn) + model.setupApplyButton(btn) } - } else { - dialogApi.setupButton(ButtonType("ZIP")) { btn -> - btn.maxWidth = Double.MAX_VALUE - btn.styleClass.addAll("btn", "btn-regular") - model.setupToggleSourceButton(btn) + + if (!isFromSettings) { + // If we show this dialog on start-up, we allow for skipping the update and add the appropriate button. + // This button will also behave like "close" button if we install the update. + dialogApi.setupButton(ButtonType.CLOSE) { btn -> + ButtonBar.setButtonUniformSize(btn, false) + btn.maxWidth = Double.MAX_VALUE + btn.styleClass.add("btn") + model.setupCloseButton(btn) + } + } else { + dialogApi.setupButton(ButtonType("ZIP")) { btn -> + btn.maxWidth = Double.MAX_VALUE + btn.styleClass.addAll("btn", "btn-regular") + model.setupToggleSourceButton(btn) + } } } - dialogContent.center = installFromChannelUi.node dialogApi.setContent(dialogContent) dialogApi.setButtonPaneNode(installFromChannelUi.progressLabel) diff --git a/ganttproject/src/main/java/biz/ganttproject/platform/UpdateDialogModel.kt b/ganttproject/src/main/java/biz/ganttproject/platform/UpdateDialogModel.kt index 05eae55b60..315c8adce9 100644 --- a/ganttproject/src/main/java/biz/ganttproject/platform/UpdateDialogModel.kt +++ b/ganttproject/src/main/java/biz/ganttproject/platform/UpdateDialogModel.kt @@ -53,7 +53,7 @@ import org.eclipse.core.runtime.Platform as Eclipsito import biz.ganttproject.platform.UpdateDialogLocalizationKeys as Keys internal enum class ApplyAction { - INSTALL_FROM_CHANNEL, INSTALL_FROM_ZIP, DOWNLOAD_MAJOR, RESTART + UP_TO_DATE, INSTALL_FROM_CHANNEL, INSTALL_FROM_ZIP, DOWNLOAD_MAJOR, RESTART } typealias InstallProgressMonitor = (Int)->Unit @@ -113,6 +113,9 @@ class UpdateDialogModel( internal var state: ApplyAction = ApplyAction.INSTALL_FROM_CHANNEL set(value) { when (value) { + ApplyAction.UP_TO_DATE -> { + btnApplyText.value = "NO ACTION" + } ApplyAction.INSTALL_FROM_CHANNEL -> { btnApplyText.value = localizer.formatText(Keys.BUTTON_OK) btnCloseText.value = localizer.formatText("button.close_skip") @@ -147,6 +150,8 @@ class UpdateDialogModel( state = ApplyAction.DOWNLOAD_MAJOR } else if (hasMinorUpdates) { state = ApplyAction.INSTALL_FROM_CHANNEL + } else { + state = ApplyAction.UP_TO_DATE } } @@ -202,6 +207,7 @@ class UpdateDialogModel( private suspend fun onApplyPressed() { when (state) { + ApplyAction.UP_TO_DATE -> {} ApplyAction.INSTALL_FROM_CHANNEL -> { applyMinorUpdates() } diff --git a/ganttproject/src/main/java/biz/ganttproject/settings/SettingsDialog.kt b/ganttproject/src/main/java/biz/ganttproject/settings/SettingsDialog.kt new file mode 100644 index 0000000000..9a25d5c227 --- /dev/null +++ b/ganttproject/src/main/java/biz/ganttproject/settings/SettingsDialog.kt @@ -0,0 +1,161 @@ +/* + * Copyright 2024 BarD Software s.r.o., Dmitry Barashev. + * + * 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 biz.ganttproject.settings + +import biz.ganttproject.FXUtil +import biz.ganttproject.FxUiComponent +import biz.ganttproject.app.RootLocalizer +import biz.ganttproject.app.dialog +import biz.ganttproject.colorFromUiManager +import biz.ganttproject.core.option.ObservableObject +import biz.ganttproject.ganttview.* +import biz.ganttproject.ganttview.ItemListDialogPane +import javafx.beans.property.BooleanProperty +import javafx.beans.property.SimpleBooleanProperty +import javafx.collections.FXCollections +import javafx.embed.swing.SwingNode +import javafx.geometry.Insets +import javafx.scene.Node +import javafx.scene.control.Label +import javafx.scene.layout.Background +import javafx.scene.layout.BackgroundFill +import javafx.scene.layout.BorderPane +import javafx.scene.layout.CornerRadii +import net.sourceforge.ganttproject.IGanttProject +import net.sourceforge.ganttproject.action.CancelAction +import net.sourceforge.ganttproject.gui.UIFacade +import net.sourceforge.ganttproject.gui.options.OptionPageProviderPanel +import net.sourceforge.ganttproject.gui.options.model.OptionPageProvider +import net.sourceforge.ganttproject.plugins.PluginManager + +data class OptionPageItem( + override var title: String, + override val isEnabledProperty: BooleanProperty = SimpleBooleanProperty(true), + val provider: OptionPageProvider?, + val project: IGanttProject, + val uiFacade: UIFacade, + val isHeader: Boolean = provider == null +): Item { + + val fxNode by lazy { + provider?.let { + if (it is FxUiComponent) { + it.buildNode() + } else { + SwingNode().also { + it.content = OptionPageProviderPanel(provider, project, uiFacade).component + } + } + } ?: Label(title) + } +} + +class OptionPageUi(editItem: ObservableObject, var resize: ()->Unit): ItemEditorPane { + private val borderPane = BorderPane() + override val node: Node + get() = borderPane + + init { + editItem.addWatcher { event -> + event.newValue?.let { + if (it != event.oldValue) { + FXUtil.transitionCenterPane(borderPane, it.fxNode) { + FXUtil.runLater(500) { + resize() + it.fxNode.requestFocus() + if (borderPane.width != 0.0) { + resize = {} + } + } + } + it.provider?.setActive(true) + } + } + } + } + override fun focus() { + } +} + +class SettingsDialogFx(private val project: IGanttProject, + private val uiFacade: UIFacade, + private val pageOrderKey: String, + private val titleKey: String) { + fun showSettingsDialog() { + dialog(id = "settings", title = ourLocalizer.formatText(titleKey)) { dialogApi -> + val listItems = FXCollections.observableArrayList(getPageProviders()) + val editItem = ObservableObject("", null) + val dialogModel = ItemListDialogModel( + listItems, + newItemFactory = { null }, + ourLocalizer + ) + dialogModel.btnApplyController.onAction = { + listItems.forEach { it.provider?.commit() } + } + + + val dialogPane = ItemListDialogPane( + listItems, + editItem, + { item -> ShowHideListItem( + { item.title }, + { true }, + { }, + if (item.isHeader) "settings-item-header" else "settings-item-page", + false + )}, + dialogModel, + OptionPageUi(editItem, dialogApi::resize), + ourLocalizer + ) + + dialogApi.addStyleClass("dlg-settings") + dialogApi.addStyleSheet( + "/biz/ganttproject/app/Dialog.css", + "/biz/ganttproject/app/Util.css", + "/biz/ganttproject/settings/SettingsDialog.css" + ) + + dialogPane.isHeaderEnabled = false + dialogPane.isAddRemoveEnabled = false + dialogPane.contentNode.background = Background(BackgroundFill("Panel.background".colorFromUiManager(), CornerRadii.EMPTY, Insets.EMPTY)) + dialogPane.build(dialogApi) + + dialogApi.setupButton(CancelAction.create("cancel") {}) + } + } + + private fun getPageProviders() = + ourLocalizer.formatText(pageOrderKey).split(",").map { pageId -> + if (pageId.startsWith("pageGroup.")) { + OptionPageItem(title = ourLocalizer.formatText(pageId), provider = null, project = project, uiFacade = uiFacade) + } else { + ourProviders.first { it.pageID == pageId }.let { provider -> + OptionPageItem(provider.toString(), provider = provider, project = project, uiFacade = uiFacade) + } + } + }.toList() +} + + +private val ourProviders = PluginManager.getExtensions("net.sourceforge.ganttproject.OptionPageProvider", +OptionPageProvider::class.java) + +private val ourLocalizer = RootLocalizer \ No newline at end of file diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/edit/SettingsDialogAction.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/action/edit/SettingsDialogAction.java index 00a547770c..3a142d94e1 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/action/edit/SettingsDialogAction.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/action/edit/SettingsDialogAction.java @@ -18,10 +18,10 @@ of the License, or (at your option) any later version. */ package net.sourceforge.ganttproject.action.edit; +import biz.ganttproject.settings.SettingsDialogFx; import net.sourceforge.ganttproject.IGanttProject; import net.sourceforge.ganttproject.action.GPAction; import net.sourceforge.ganttproject.gui.UIFacade; -import net.sourceforge.ganttproject.gui.options.SettingsDialog2; import java.awt.event.ActionEvent; @@ -44,7 +44,8 @@ public void actionPerformed(ActionEvent e) { if (calledFromAppleScreenMenu(e)) { return; } - SettingsDialog2 dialog = new SettingsDialog2(myProject, myUiFacade, "settings.app.pageOrder", "settings.app"); - dialog.show(); + //SettingsDialog2 dialog = new SettingsDialog2(myProject, myUiFacade, "settings.app.pageOrder", "settings.app"); + //dialog.show(); + new SettingsDialogFx(myProject,myUiFacade, "settings.app.pageOrder", "settings.app").showSettingsDialog(); } } diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderBase.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderBase.java index feb637c0f6..e056aa5855 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderBase.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderBase.java @@ -32,6 +32,7 @@ public abstract class OptionPageProviderBase implements OptionPageProvider { private String myPageID; private IGanttProject myProject; private UIFacade myUiFacade; + private boolean isActive; protected OptionPageProviderBase(String pageID) { myPageID = pageID; @@ -67,8 +68,10 @@ public void commit() { @Override public void setActive(boolean isActive) { + this.isActive = isActive; } + protected boolean isActive() { return this.isActive; } @Override public abstract GPOptionGroup[] getOptionGroups(); diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderPanel.java b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderPanel.java index 7088a301a1..c52cb921f5 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderPanel.java +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/gui/options/OptionPageProviderPanel.java @@ -41,7 +41,7 @@ public OptionPageProviderPanel(OptionPageProvider provider, IGanttProject projec myGroups = myProvider.getOptionGroups(); } - public Component getComponent() { + public JComponent getComponent() { JComponent providerComponent; if (myProvider.hasCustomComponent()) { providerComponent = (JComponent) myProvider.buildPageComponent(); @@ -51,9 +51,8 @@ public Component getComponent() { providerComponent = builder.buildPage(myGroups, myProvider.getPageID()); } providerComponent.setBorder(new EmptyBorder(5, 5, 5, 5)); - Component result = providerComponent; //JScrollPane result = new JScrollPane(providerComponent); - return result; + return providerComponent; } // public boolean applyChanges(boolean askForApply) { diff --git a/ganttproject/src/main/sass/biz/ganttproject/ganttview/ListViewEditorDialog.scss b/ganttproject/src/main/sass/biz/ganttproject/ganttview/ListViewEditorDialog.scss index d6ee80c4d8..773b0c30a5 100644 --- a/ganttproject/src/main/sass/biz/ganttproject/ganttview/ListViewEditorDialog.scss +++ b/ganttproject/src/main/sass/biz/ganttproject/ganttview/ListViewEditorDialog.scss @@ -13,11 +13,11 @@ -fx-border-width: 0 1 0 0; -fx-border-color: $gp-light-gray; -fx-min-width: 20em; + -fx-font-size: 100%; } .column-item-cell { -fx-fill: $gp-dark-gray; -fx-text-fill: $gp-dark-gray; - -fx-font-size: 100%; -fx-padding: 5 10 5 15; -fx-strikethrough: false; .glyph-icon { diff --git a/ganttproject/src/main/sass/biz/ganttproject/settings/SettingsDialog.scss b/ganttproject/src/main/sass/biz/ganttproject/settings/SettingsDialog.scss new file mode 100644 index 0000000000..ca63fb5b73 --- /dev/null +++ b/ganttproject/src/main/sass/biz/ganttproject/settings/SettingsDialog.scss @@ -0,0 +1,62 @@ +/* + * Copyright 2024 BarD Software s.r.o., Dmitry Barashev. + * + * 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 . + */ +@import "../app/theme"; +.dlg-list-view-editor.dlg-settings { + .content-pane { + .list-view { + -fx-background-color: white; + -fx-border-width: 0; + } + .column-item-cell { + &.settings-item-header { + -fx-font-weight: 900; + -fx-font-size: 110%; + -fx-background-color: $gp-zebra-gray; + -fx-border-color: $gp-orange; + -fx-border-width: 0 0 2 0; + -fx-padding: 5 10 5 5; + -fx-background-insets: 1ex 0 0 0; + -fx-border-insets: 10 0 2 0; + } + &.settings-item-page { + -fx-fill: $gp-dark-gray; + -fx-text-fill: $gp-dark-gray; + &:hover { + -fx-cursor: hand; + -fx-fill: $gp-medium-gray; + -fx-border-color: transparent; + -fx-border-width: 0 0 0 2; + } + &:selected { + -fx-border-color: $gp-medium-gray; + -fx-border-width: 0 0 0 2; + } + + } + &.settings-item-page { + -fx-background-color: white; + } + } + .column-item-cell-empty { + -fx-background-color: white; + } + } +} +//.column-item-cell.settings-item-header { +//}