diff --git a/cloud.ganttproject.colloboque/build.gradle.kts b/cloud.ganttproject.colloboque/build.gradle.kts index fca722861f..314336e92a 100644 --- a/cloud.ganttproject.colloboque/build.gradle.kts +++ b/cloud.ganttproject.colloboque/build.gradle.kts @@ -23,8 +23,8 @@ repositories { } dependencies { - implementation("biz.ganttproject:biz.ganttproject.core:23.+") - implementation("biz.ganttproject:ganttproject:23.+") + implementation("biz.ganttproject:biz.ganttproject.core:24.+") + implementation("biz.ganttproject:ganttproject:24.+") implementation("ch.qos.logback:logback-classic:1.2.11") implementation("com.google.guava:guava:31.1-jre") diff --git a/ganttproject-builder/VERSION b/ganttproject-builder/VERSION index 4815bb3afa..458440bffe 100644 --- a/ganttproject-builder/VERSION +++ b/ganttproject-builder/VERSION @@ -1 +1 @@ -3.3.3300 \ No newline at end of file +3.3.3301 diff --git a/ganttproject/src/main/java/biz/ganttproject/app/Internationalization.kt b/ganttproject/src/main/java/biz/ganttproject/app/Internationalization.kt index 975fcf133f..2298646c55 100644 --- a/ganttproject/src/main/java/biz/ganttproject/app/Internationalization.kt +++ b/ganttproject/src/main/java/biz/ganttproject/app/Internationalization.kt @@ -61,11 +61,17 @@ class LocalizedString( return this } + fun update(vararg args: Any): LocalizedString { + this.args = args.map { it.toString() }.toList() + observable.value = build() + return this + } + internal fun update() { observable.value = build() } - private fun build(): String = i18n.formatText(key, *args.toTypedArray()) + private fun build(): String = i18n.formatText(key, *(args.toTypedArray())) } /** @@ -172,11 +178,11 @@ open class DefaultLocalizer( * This localizer searches for key values in a map. The map values are lambdas which * allows for values calculation. */ -class MappingLocalizer(val key2lambda: MapString)?>, val unhandledKey: (String)->String?) : Localizer { +class MappingLocalizer(val key2lambda: MapLocalizedString?>, val unhandledKey: (String)->LocalizedString?) : Localizer { override fun create(key: String) = LocalizedString(key, this) override fun formatTextOrNull(key: String, vararg args: Any): String? = - key2lambda[key]?.invoke() ?: unhandledKey(key) + key2lambda[key]?.invoke()?.update(*args)?.value ?: unhandledKey(key)?.update(*args)?.value } /** diff --git a/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt b/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt index c412a677dd..b9b8f6d32d 100644 --- a/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt +++ b/ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt @@ -264,17 +264,29 @@ internal class CustomPropertyEditor( ) { private val localizer = run { val fallback1 = MappingLocalizer(mapOf()) { - if (it.endsWith(".label")) { - val key = it.split('.', limit = 2)[0] - RootLocalizer.formatText(key) - } else { - null + when { + it.endsWith(".label") -> { + val key = it.split('.', limit = 2)[0] + RootLocalizer.create(key) + } + it == "columnExists" -> RootLocalizer.create(it) + else -> null } } val fallback2 = RootLocalizer.createWithRootKey("option.taskProperties.customColumn", fallback1) RootLocalizer.createWithRootKey("option.customPropertyDialog", fallback2) } - private val nameOption = ObservableString(id = "name") + private val nameOption = ObservableString(id = "name", validator = {value -> + listItems.find { it.title == value }?.let { + if (it != selectedItem?.cloneOf) { + throw ValidationException(localizer.formatText("columnExists", value)) + } + } + if (value.isBlank()) { + throw ValidationException(localizer.formatText("name.validation.empty")) + } + value + }) private val typeOption = ObservableEnum(id ="type", initValue = PropertyType.STRING, allValues = PropertyType.values()) private val defaultValueOption = ObservableString( id = "defaultValue", @@ -456,6 +468,7 @@ private class CellImpl : ListCell() { init { styleClass.add("column-item-cell") + alignment = Pos.CENTER_LEFT } override fun updateItem(item: ColumnAsListItem?, empty: Boolean) { @@ -466,14 +479,19 @@ private class CellImpl : ListCell() { return } text = item.title + if (text.isEmpty()) { + text = " " + } 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) } @@ -531,7 +549,7 @@ private fun showColumnManager(columnList: ColumnList, customColumnsManager: Cust dlg.hide() } } - dlg.setupButton(ButtonType.CANCEL) { btn -> + dlg.setupButton(ButtonType.NEXT) { btn -> btn.text = localizer.formatText("add") ButtonBar.setButtonData(btn, ButtonBar.ButtonData.HELP) btn.disableProperty().bind(columnManager.btnAddController.isDisabled) @@ -541,7 +559,7 @@ private fun showColumnManager(columnList: ColumnList, customColumnsManager: Cust } btn.styleClass.addAll("btn-attention", "secondary") } - dlg.setupButton(ButtonType.CANCEL) { btn -> + dlg.setupButton(ButtonType.NEXT) { btn -> btn.text = localizer.formatText("delete") ButtonBar.setButtonData(btn, ButtonBar.ButtonData.HELP_2) btn.disableProperty().bind(columnManager.btnDeleteController.isDisabled) diff --git a/ganttproject/src/main/java/biz/ganttproject/print/PrintUi.kt b/ganttproject/src/main/java/biz/ganttproject/print/PrintUi.kt index b382a68de5..883d270715 100644 --- a/ganttproject/src/main/java/biz/ganttproject/print/PrintUi.kt +++ b/ganttproject/src/main/java/biz/ganttproject/print/PrintUi.kt @@ -119,11 +119,11 @@ fun showPrintDialog(activeChart: Chart, preferences: Preferences) { ) add( DateRangePicker(previews.dateRangeModel, MappingLocalizer(mapOf( - "custom" to { prefixedLocalizer.formatText("dateRange.custom") }, - "view" to { prefixedLocalizer.formatText("dateRange.currentView") }, - "project" to { i18n.formatText("wholeProject") } + "custom" to { prefixedLocalizer.create("dateRange.custom") }, + "view" to { prefixedLocalizer.create("dateRange.currentView") }, + "project" to { i18n.create("wholeProject") } )) { key -> - i18n.formatText(key) + i18n.create(key) }).component ) } diff --git a/ganttproject/src/main/sass/biz/ganttproject/ganttview/ColumnManager.scss b/ganttproject/src/main/sass/biz/ganttproject/ganttview/ColumnManager.scss index 2d85defaf5..ca8309dc6f 100644 --- a/ganttproject/src/main/sass/biz/ganttproject/ganttview/ColumnManager.scss +++ b/ganttproject/src/main/sass/biz/ganttproject/ganttview/ColumnManager.scss @@ -19,7 +19,6 @@ -fx-font-size: 100%; -fx-padding: 5 10 5 15; -fx-strikethrough: false; - .glyph-icon { -glyph-size: 18; -fx-alignment: center; @@ -30,6 +29,9 @@ -glyph-size: 18; } } + &.is-visible { + -fx-font-weight: bold; + } &.is-hidden > * { -fx-fill: lighten($gp-medium-gray, 20%); } diff --git a/ganttproject/src/test/java/biz/ganttproject/app/InternationalizationTest.kt b/ganttproject/src/test/java/biz/ganttproject/app/InternationalizationTest.kt index 4dd14122cf..7e3c15f6be 100644 --- a/ganttproject/src/test/java/biz/ganttproject/app/InternationalizationTest.kt +++ b/ganttproject/src/test/java/biz/ganttproject/app/InternationalizationTest.kt @@ -82,6 +82,23 @@ class InternationalizationTest { assertEquals("Hello, World!", i18n.formatText("hello", i18n.formatText("world"))) assertEquals("Hello, GanttProject!", i18n.formatText("hello", i18n.formatText("ganttproject"))) } + + @Test fun `mapping localizer`() { + val rootLocalizer = DefaultLocalizer( + currentTranslation = newResourceBundle(mapOf( + "foo.hello" to "Hello, {0}", + ))) + val mappingLocalizer = MappingLocalizer(mapOf( + "helloWorld" to { rootLocalizer.create("foo.hello") } + )) { + LocalizedString("Unknown String", DummyLocalizer) + } + + assertEquals("Hello, Local World", mappingLocalizer.formatText("helloWorld", "Local World")) + assertEquals("Unknown String", mappingLocalizer.formatText("helloUniverse")) + } + + } private fun newResourceBundle(kv: Map) : SimpleObjectProperty {