Skip to content

Commit

Permalink
Merge pull request #2557 from bardsoftware/tkt-2514-fix-bugs
Browse files Browse the repository at this point in the history
Some bugfixes in the custom columns and filters
  • Loading branch information
dbarashev authored Sep 23, 2024
2 parents 0d21b2a + a7036a9 commit dfc71b2
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 150 deletions.
2 changes: 1 addition & 1 deletion biz.ganttproject.app.localization
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,20 @@ data class XmlView(
)
}

/**
* Task filter serialization.
*/
data class XmlFilter(
@get:JacksonXmlProperty(isAttribute = true)
var title: String = "",

@get:JacksonXmlProperty(isAttribute = true)
@get:JacksonXmlProperty(isAttribute = true, localName = "is-built-in")
var isBuiltIn: Boolean = false,

@get:JacksonXmlProperty(isAttribute = true)
var description: String = "",

@get:JacksonXmlProperty(isAttribute = true)
@get:JacksonXmlProperty(isAttribute = true, localName = "is-enabled")
var isEnabled: Boolean = false,

@get:JsonInclude(JsonInclude.Include.NON_NULL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ of the License, or (at your option) any later version.

import biz.ganttproject.core.table.ColumnList;
import biz.ganttproject.core.table.ColumnList.Column;
import biz.ganttproject.customproperty.CustomPropertyClass;

import javax.swing.*;
import java.awt.*;
Expand Down Expand Up @@ -136,6 +137,18 @@ public String getName() {
return ourLocaleApi == null ? getNameKey() : ourLocaleApi.i18n(getNameKey());
}

public CustomPropertyClass getCustomPropertyClass() {
if (myValueClass == Integer.class) {
return CustomPropertyClass.INTEGER;
}
if (myValueClass == BigDecimal.class) {
return CustomPropertyClass.DOUBLE;
}
if (myValueClass == GregorianCalendar.class) {
return CustomPropertyClass.DATE;
}
return CustomPropertyClass.TEXT;
}
public static class Functions {
static Predicate<Object> NOT_EDITABLE = input -> false;
static Predicate<Object> ALWAYS_EDITABLE = input -> true;
Expand Down
145 changes: 36 additions & 109 deletions ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ColumnManager(
internal val dialogModel: ItemListDialogModel<ColumnAsListItem> = ItemListDialogModel<ColumnAsListItem>(
listItems,
newItemFactory = {
ColumnAsListItem(null, isVisible = true, isCustom = true, customColumnsManager, {customPropertyEditor.updateVisibility(it)})
ColumnAsListItem(null, isVisible = true, isCustom = true, customColumnsManager)
},
ourLocalizer
)
Expand All @@ -64,39 +64,41 @@ class ColumnManager(
calculationMethodValidator = calculationMethodValidator,
expressionAutoCompletion = expressionAutoCompletion,
nameClash = { tryName ->
listItems.find { it.title == tryName && it != selectedItem.value?.cloneOf } != null
listItems.find { it.title == tryName && it != selectedItem.value } != null
},
localizer = ourEditorLocalizer
),
dialogModel = dialogModel
)

private val mergedColumns: MutableList<ColumnList.Column> = mutableListOf()
internal val dialogPane = ItemListDialogPane<ColumnAsListItem>(
listItems = listItems,
selectedItem = selectedItem,
listItemConverter = { ShowHideListItem(it.title, it.isVisibleProperty) },
dialogModel = dialogModel,
editor = customPropertyEditor,
localizer = ourEditorLocalizer
)

init {
mergedColumns.addAll(currentTableColumns.exportData())
// First go columns which are shown in the table now
// and they are ordered the same way are shown in the table.
listItems.setAll(mergedColumns.sortedWith { col1, col2 -> columnsOrder(col1, col2) }.map { col ->
val isCustom = customColumnsManager.definitions.find { it.id == col.id } != null
ColumnAsListItem(col, col.isVisible, isCustom, customColumnsManager, customPropertyEditor::updateVisibility)
ColumnAsListItem(col, col.isVisible, isCustom, customColumnsManager)
})
customColumnsManager.definitions.forEach { def ->
if (mergedColumns.find { it.id == def.id } == null) {
val columnStub = ColumnList.ColumnStub(def.id, def.name, false, -1, -1)
mergedColumns.add(columnStub)
listItems.add(ColumnAsListItem(columnStub, columnStub.isVisible, true, customColumnsManager, customPropertyEditor::updateVisibility))
listItems.add(ColumnAsListItem(columnStub, columnStub.isVisible, true, customColumnsManager))
}
}
}

internal val dialogPane = ItemListDialogPane<ColumnAsListItem>(
listItems = listItems,
selectedItem = selectedItem,
listItemConverter = { ShowHideListItem( {it.title}, {it.isEnabledProperty.value}, {it.isEnabledProperty.set(!it.isEnabledProperty.value)}) },
dialogModel = dialogModel,
editor = customPropertyEditor,
localizer = ourEditorLocalizer
)

internal fun onApply() {
when (applyExecutor) {
ApplyExecutorType.DIRECT -> applyChanges()
Expand All @@ -115,14 +117,14 @@ class ColumnManager(
listItems.forEach { columnItem ->
columnItem.column?.let {
// Apply changes made in the columns which has existed before.
it.isVisible = columnItem.isVisible
it.isVisible = columnItem.isEnabledProperty.value
if (columnItem.isCustom) {
customColumnsManager.definitions.find { def -> def.id == it.id }?.importColumnItem(columnItem)
}
} ?: run {
// Create custom columns which were added in the dialog
val def = customColumnsManager.createDefinition(columnItem.type.getCustomPropertyClass(), columnItem.title, columnItem.defaultValue)
if (columnItem.isVisible) {
val def = customColumnsManager.createDefinition(columnItem.type, columnItem.title, columnItem.defaultValue)
if (columnItem.isEnabledProperty.value) {
mergedColumns.add(ColumnList.ColumnStub(def.id, def.name, true, mergedColumns.size, 50))
}
if (columnItem.isCalculated) {
Expand All @@ -136,62 +138,26 @@ class ColumnManager(
}
}


internal enum class PropertyType(private val displayName: String) {
STRING("text"),
INTEGER("integer"),
DATE("date"),
DECIMAL("double"),
BOOLEAN("boolean");

override fun toString() = this.displayName
}

internal fun CustomPropertyDefinition.getPropertyType(): PropertyType = when (this.propertyClass) {
CustomPropertyClass.TEXT -> PropertyType.STRING
CustomPropertyClass.DATE -> PropertyType.DATE
CustomPropertyClass.INTEGER -> PropertyType.INTEGER
CustomPropertyClass.DOUBLE -> PropertyType.DECIMAL
CustomPropertyClass.BOOLEAN -> PropertyType.BOOLEAN
}

internal fun PropertyType.getCustomPropertyClass(): CustomPropertyClass = when (this) {
PropertyType.STRING -> CustomPropertyClass.TEXT
PropertyType.INTEGER -> CustomPropertyClass.INTEGER
PropertyType.DATE -> CustomPropertyClass.DATE
PropertyType.BOOLEAN -> CustomPropertyClass.BOOLEAN
PropertyType.DECIMAL -> CustomPropertyClass.DOUBLE
}

internal fun PropertyType.createValidator(): ValueValidator<*> = when (this) {
PropertyType.INTEGER -> integerValidator
PropertyType.DECIMAL -> doubleValidator
PropertyType.DATE -> createStringDateValidator {
internal fun CustomPropertyClass.createValidator(): ValueValidator<*> = when (this) {
CustomPropertyClass.INTEGER -> integerValidator
CustomPropertyClass.DOUBLE -> doubleValidator
CustomPropertyClass.DATE -> createStringDateValidator {
listOf(GanttLanguage.getInstance().shortDateFormat, GanttLanguage.getInstance().mediumDateFormat)
}
else -> voidValidator
}

internal fun CustomPropertyDefinition.importColumnItem(item: ColumnAsListItem) {
this.name = item.title
if (item.defaultValue.trim().isNotBlank()) {
this.defaultValueAsString = item.defaultValue
}
this.propertyClass = item.type.getCustomPropertyClass()
this.propertyClass = item.type
this.defaultValueAsString = item.defaultValue.trim().ifBlank { null }
if (item.isCalculated) {
this.calculationMethod = SimpleSelect(propertyId = this.id, selectExpression = item.expression, resultClass = this.propertyClass.javaClass)
} else {
this.calculationMethod = null
}
}

internal fun TaskDefaultColumn.getPropertyType(): PropertyType = when (this) {
TaskDefaultColumn.ID, TaskDefaultColumn.DURATION, TaskDefaultColumn.COMPLETION -> PropertyType.INTEGER
TaskDefaultColumn.BEGIN_DATE, TaskDefaultColumn.END_DATE -> PropertyType.DATE
TaskDefaultColumn.COST -> PropertyType.DECIMAL
else -> PropertyType.STRING
}

/**
* This the model of the property editor. It encapsulates the values that are being edited and their validators.
*/
Expand All @@ -210,7 +176,7 @@ internal class EditorModel(
}
value
})
val typeOption = ObservableEnum(id = "type", initValue = PropertyType.STRING, allValues = PropertyType.values())
val typeOption = ObservableEnum(id = "type", initValue = CustomPropertyClass.TEXT, allValues = CustomPropertyClass.entries.toTypedArray())
val defaultValueOption = ObservableString(
id = "defaultValue",
initValue = "",
Expand All @@ -232,7 +198,7 @@ internal class EditorModel(
if (it.isNotBlank()) {
calculationMethodValidator.validate(
// Incomplete instance just for validation purposes
SimpleSelect(propertyId = "", selectExpression = it, resultClass = typeOption.value.getCustomPropertyClass().javaClass)
SimpleSelect(propertyId = "", selectExpression = it, resultClass = typeOption.value.javaClass)
)
it
} else {
Expand All @@ -251,7 +217,7 @@ internal class EditorModel(
*/
internal class CustomPropertyEditor(
private val model: EditorModel,
dialogModel: ItemListDialogModel<ColumnAsListItem>,
private val dialogModel: ItemListDialogModel<ColumnAsListItem>,
selectedItemProperty: ObservableProperty<ColumnAsListItem?>,
private val btnDeleteController: BtnController<Unit>,
escCloseEnabled: BooleanProperty,
Expand All @@ -266,7 +232,7 @@ internal class CustomPropertyEditor(
model.nameOption.set(item.title)
model.typeOption.set(item.type)
model.defaultValueOption.set(item.defaultValue)
visibilityToggle.isSelected = item.isVisible
visibilityToggle.isSelected = item.isEnabledProperty.value

if (item.isCustom) {
propertySheetLabel.text = ourLocalizer.formatText("propertyPane.title.custom")
Expand All @@ -285,31 +251,18 @@ internal class CustomPropertyEditor(
}

override fun saveData(item: ColumnAsListItem) {
item.isVisible = visibilityToggle.isSelected
item.isEnabledProperty.set(visibilityToggle.isSelected)
item.title = model.nameOption.value ?: ""
item.type = model.typeOption.value
item.defaultValue = model.defaultValueOption.value ?: ""
item.isCalculated = model.isCalculatedOption.value
item.expression = model.expressionOption.value ?: ""
dialogModel.requireRefresh.set(true)
}

init {
model.allOptions.forEach { it.addWatcher { onEdit() } }

visibilityToggle.selectedProperty().addListener { _, _, _ ->
onEdit()
}
}

internal fun updateVisibility(item: ColumnAsListItem) {
editItem.value?.let {
if (it.column?.id == item.column?.id && it.isVisible != item.isVisible) {
it.isVisible = item.isVisible
visibilityToggle.isSelected = item.isVisible
}
}
}

}

/**
Expand All @@ -320,37 +273,13 @@ internal class ColumnAsListItem(
isVisible: Boolean,
val isCustom: Boolean,
val customColumnsManager: CustomPropertyManager,
val changeListener: (ColumnAsListItem)->Unit,
override val cloneOf: ColumnAsListItem? = null
): Item<ColumnAsListItem> {

constructor(cloneOf: ColumnAsListItem): this(
cloneOf.column, cloneOf.isVisible, cloneOf.isCustom, cloneOf.customColumnsManager,
cloneOf.changeListener, cloneOf) {

this.title = cloneOf.title
this.type = cloneOf.type
this.defaultValue = cloneOf.defaultValue
this.isCalculated = cloneOf.isCalculated
this.expression = cloneOf.expression
}

val isVisibleProperty = ObservableBoolean("", isVisible).also {
it.addWatcher { changeListener(this@ColumnAsListItem) }
}

var isVisible: Boolean
set(value) {
if (isVisibleProperty.value != value) {
isVisibleProperty.set(value)
changeListener(this)
}
}
get() = isVisibleProperty.value
override val isEnabledProperty: BooleanProperty = SimpleBooleanProperty(isVisible)

override var title: String = ""

var type: PropertyType = PropertyType.STRING
var type: CustomPropertyClass = CustomPropertyClass.TEXT

var defaultValue: String = ""

Expand All @@ -363,20 +292,17 @@ internal class ColumnAsListItem(
return "ColumnAsListItem(title='$title')"
}

override fun clone(): ColumnAsListItem = ColumnAsListItem(this)

init {
this.isVisible = isVisible
if (column != null) {
title = column.name
val customColumn = customColumnsManager.definitions.find { it.id == column.id }
type = run {
if (isCustom) {
customColumn?.getPropertyType()
customColumn?.propertyClass
} else {
TaskDefaultColumn.find(column?.id)?.getPropertyType()
TaskDefaultColumn.find(column?.id)?.customPropertyClass
}
} ?: PropertyType.STRING
} ?: CustomPropertyClass.TEXT
defaultValue =
if (!isCustom) ""
else customColumn?.defaultValueAsString ?: ""
Expand Down Expand Up @@ -452,5 +378,6 @@ internal val ourEditorLocalizer = run {
}
val fallback2 = RootLocalizer.createWithRootKey("option.taskProperties.customColumn", fallback1)
val fallback3 = RootLocalizer.createWithRootKey("option.customPropertyDialog", fallback2)
RootLocalizer.createWithRootKey("", fallback3)
val fallback4 = RootLocalizer.createWithRootKey("customPropertyDialog", fallback3)
RootLocalizer.createWithRootKey("", fallback4)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import biz.ganttproject.app.MappingLocalizer
import biz.ganttproject.app.RootLocalizer
import biz.ganttproject.app.dialog
import biz.ganttproject.core.option.Completion
import biz.ganttproject.core.option.DefaultBooleanOption
import biz.ganttproject.core.option.ObservableObject
import biz.ganttproject.core.option.ObservableString
import biz.ganttproject.core.option.ValidationException
Expand All @@ -49,11 +48,11 @@ fun showFilterDialog(filterManager: TaskFilterManager, projectDatabase: ProjectD
dialogModel.btnApplyController.onAction = {
filterManager.importFilters(listItems)
}
val editor = FilterEditor(editorModel, editItem, dialogModel)
val editor = FilterEditor(dialogModel, editorModel, editItem, dialogModel)
val dialogPane = ItemListDialogPane<TaskFilter>(
listItems,
editItem,
{ filter -> ShowHideListItem(filter.title, filter.isEnabledProperty) },
{ filter -> ShowHideListItem({filter.title}, {filter.isEnabledProperty.value}, {filter.isEnabledProperty.set(!filter.isEnabledProperty.get())}) },
dialogModel,
editor,
i18n
Expand Down Expand Up @@ -99,7 +98,11 @@ internal class FilterEditorModel(
* Editor pane for editing the selected filter properties.
*/
internal class FilterEditor(
private val editorModel: FilterEditorModel, editItem: ObservableObject<TaskFilter?>, model: ItemListDialogModel<TaskFilter>)
private val dialogModel: ItemListDialogModel<TaskFilter>,
private val editorModel: FilterEditorModel,
editItem: ObservableObject<TaskFilter?>,
model: ItemListDialogModel<TaskFilter>
)
: ItemEditorPane<TaskFilter>(
editorModel.fields, editItem, model, i18n
) {
Expand All @@ -109,10 +112,13 @@ internal class FilterEditor(
editorModel.descriptionField.set(item.description)
editorModel.expressionField.set(item.expression)
propertySheet.isDisable = item.isBuiltIn
dialogModel.btnDeleteController.isDisabled.set(item.isBuiltIn)
} else {
editorModel.nameField.set("")
editorModel.descriptionField.set("")
editorModel.expressionField.set("")
propertySheet.isDisable = true
dialogModel.btnDeleteController.isDisabled.set(true)
}
}

Expand Down
Loading

0 comments on commit dfc71b2

Please sign in to comment.