Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some bugfixes in the custom columns and filters #2557

Merged
merged 4 commits into from
Sep 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biz.ganttproject.app.localization
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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.*;
@@ -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;
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
@@ -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
)
@@ -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()
@@ -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) {
@@ -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.
*/
@@ -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 = "",
@@ -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 {
@@ -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,
@@ -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")
@@ -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
}
}
}

}

/**
@@ -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 = ""

@@ -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 ?: ""
@@ -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
@@ -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
@@ -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
@@ -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
) {
@@ -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)
}
}

Original file line number Diff line number Diff line change
@@ -23,12 +23,14 @@ 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
import biz.ganttproject.lib.fx.vbox
import de.jensd.fx.glyphs.materialicons.MaterialIcon
import de.jensd.fx.glyphs.materialicons.MaterialIconView
import javafx.beans.property.BooleanProperty
import javafx.beans.property.SimpleBooleanProperty
import javafx.collections.MapChangeListener
import javafx.collections.ObservableList
@@ -45,12 +47,14 @@ import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import javafx.scene.layout.StackPane
import javafx.util.Callback
import java.util.WeakHashMap

/**
* This is an object that is rendered in a list view.
*/
data class ShowHideListItem(val text: String, val isVisible: GPObservable<Boolean>)
data class ShowHideListItem(val text: ()->String, val isVisible: ()->Boolean, val toggleVisible: ()->Unit)

private val ourCells = WeakHashMap<ShowHideListCell<*>, 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.
*/
@@ -59,7 +63,7 @@ internal class ShowHideListCell<T>(private val converter: (T)-> ShowHideListItem
private val iconHidden = MaterialIconView(MaterialIcon.VISIBILITY_OFF)
private val iconPane = StackPane().also {
it.onMouseClicked = EventHandler { _ ->
theItem.isVisible.value = !theItem.isVisible.value
theItem.toggleVisible()
updateTheItem(theItem)
}
}
@@ -68,6 +72,7 @@ internal class ShowHideListCell<T>(private val converter: (T)-> ShowHideListItem
init {
styleClass.add("column-item-cell")
alignment = Pos.CENTER_LEFT
ourCells.put(this, true)
}

override fun updateItem(item: T?, empty: Boolean) {
@@ -81,14 +86,14 @@ internal class ShowHideListCell<T>(private val converter: (T)-> ShowHideListItem
}

private fun updateTheItem(item: ShowHideListItem) {
text = item.text
text = item.text()
if (text.isEmpty()) {
text = " "
}
if (graphic == null) {
graphic = iconPane
}
if (item.isVisible.value) {
if (item.isVisible()) {
styleClass.add("is-visible")
styleClass.remove("is-hidden")
iconPane.children.setAll(iconVisible)
@@ -100,6 +105,12 @@ internal class ShowHideListCell<T>(private val converter: (T)-> ShowHideListItem
}
}
}

fun refresh() {
if (item != null) {
updateTheItem(converter(item))
}
}
}

/**
@@ -130,7 +141,7 @@ internal open class ItemEditorPane<T: Item<T>>(
internal val visibilityTogglePane = HBox().also {
it.styleClass.add("visibility-pane")
it.children.add(visibilityToggle)
it.children.add(Label(localizer.formatText("customPropertyDialog.visibility.label")))
it.children.add(Label(localizer.formatText("visibility.label")))
}
internal val propertySheetLabel = Label().also {
it.styleClass.add("title")
@@ -146,10 +157,18 @@ internal open class ItemEditorPane<T: Item<T>>(

init {
fields.forEach { it.addWatcher { onEdit() } }
editItem.addWatcher {
if (it.trigger != this) {
visibilityToggle.selectedProperty().subscribe { oldValue, newValue ->
onEdit()
}
editItem.addWatcher {evt ->
if (evt.trigger != this) {
isEditIgnored = true
loadData(it.newValue)
evt.newValue?.isEnabledProperty?.addListener { source, oldValue, newValue ->
if (oldValue != newValue && source == editItem.value?.isEnabledProperty) {
visibilityToggle.isSelected = newValue
}
}
loadData(evt.newValue)
isEditIgnored = false
}
}
@@ -174,10 +193,11 @@ internal open class ItemEditorPane<T: Item<T>>(

internal fun onEdit() {
if (isEditIgnored) return
editItem.value?.clone()?.let {
editItem.value?.let {
saveData(it)
editItem.set(it, trigger = this)
//editItem.set(it, trigger = this)
}
dialogModel.requireRefresh.set(true)
}

internal fun focus() {
@@ -205,8 +225,7 @@ internal open class ItemEditorPane<T: Item<T>>(

interface Item<T> {
var title: String
val cloneOf: T?
fun clone(): T
val isEnabledProperty: BooleanProperty
}

data class BtnController<T>(
@@ -226,6 +245,7 @@ internal class ItemListDialogModel<T: Item<T>>(
val btnDeleteController = BtnController(onAction = this::onDeleteColumn)
val btnApplyController = BtnController(onAction = {})
internal var selection: ()-> Collection<T> = { emptyList() }
val requireRefresh = SimpleBooleanProperty(false)

fun onAddColumn(): T {
val item = newItemFactory()
@@ -271,22 +291,31 @@ internal class ItemListDialogPane<T: Item<T>>(
internal val listView: ListView<T> = ListView()

init {
selectedItem.addWatcher { evt ->
if (evt.trigger != listView && evt.newValue != null) {
listItems.replaceAll { if (it.title == evt.newValue?.cloneOf?.title) { evt.newValue } else { it } }
}
}
// selectedItem.addWatcher { evt ->
// if (evt.trigger != listView && evt.newValue != null) {
// listItems.replaceAll { if (it == evt.newValue?.cloneOf) { evt.newValue?.clone(forEditing = false) } else { it } }
// }
// }
listView.apply {
this@ItemListDialogPane.dialogModel.selection = { selectionModel.selectedItems }
items = listItems
cellFactory = Callback { ShowHideListCell(listItemConverter)}
selectionModel.selectedItemProperty().addListener { _, _, newValue ->
if (newValue != null) {
selectedItem.set(newValue.clone(), trigger = this)
selectedItem.set(newValue, trigger = this)
}
}
selectionModel.select(0)
}
dialogModel.requireRefresh.subscribe { oldValue, newValue ->
if (newValue == true && oldValue == false) {
// listView.refresh()
ourCells.keys.forEach {
it.refresh()
}
dialogModel.requireRefresh.set(false)
}
}
}

fun build(dlg: DialogController) {
Original file line number Diff line number Diff line change
@@ -19,12 +19,12 @@ along with GanttProject. If not, see <http://www.gnu.org/licenses/>.
package biz.ganttproject.ganttview

import biz.ganttproject.core.option.DefaultBooleanOption
import biz.ganttproject.core.option.GPObservable
import biz.ganttproject.core.option.GPOption
import biz.ganttproject.core.option.ObservableBoolean
import biz.ganttproject.core.time.CalendarFactory
import biz.ganttproject.customproperty.CustomPropertyClass
import biz.ganttproject.customproperty.SimpleSelect
import javafx.beans.property.BooleanProperty
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleIntegerProperty
import net.sourceforge.ganttproject.GPLogger
import net.sourceforge.ganttproject.storage.ColumnConsumer
@@ -41,15 +41,11 @@ import javax.swing.event.UndoableEditEvent
data class TaskFilter(
override var title: String,
var description: String,
val isEnabledProperty: GPObservable<Boolean>,
override val isEnabledProperty: BooleanProperty = SimpleBooleanProperty(false),
val filterFxn: TaskFilterFxn,
var expression: String? = null,
val isBuiltIn: Boolean = false,
override val cloneOf: TaskFilter? = null
): Item<TaskFilter> {

override fun clone(): TaskFilter = copy(cloneOf = this)
}
): Item<TaskFilter>

typealias TaskFilterFxn = (parent: Task, child: Task?) -> Boolean
typealias FilterChangedListener = (filter: TaskFilter?) -> Unit
@@ -65,7 +61,7 @@ object BuiltInFilters {
child?.completionPercentage?.let { it < 100 } != false
}
val completedTasksFilter = TaskFilter(
"filter.completedTasks", "", filterCompletedTasksOption.asObservableValue(), completedTasksFilterFxn, isBuiltIn = true
"filter.completedTasks", "", filterCompletedTasksOption.asJavafxProperty(), completedTasksFilterFxn, isBuiltIn = true
)
// ----------------------------
private val dueTodayFilterFxn: TaskFilterFxn = { _, child ->
@@ -74,15 +70,15 @@ object BuiltInFilters {
} != false
}
val dueTodayFilter = TaskFilter(
"filter.dueTodayTasks", "", filterDueTodayOption.asObservableValue(), dueTodayFilterFxn, isBuiltIn = true
"filter.dueTodayTasks", "", filterDueTodayOption.asJavafxProperty(), dueTodayFilterFxn, isBuiltIn = true
)
// ----------------------------
private val overdueFilterFxn: TaskFilterFxn = { _, child ->
child?.let { it.completionPercentage < 100 && it.endsBeforeToday()
} != false
}
val overdueFilter = TaskFilter(
"filter.overdueTasks", "", filterOverdueOption.asObservableValue(), overdueFilterFxn, isBuiltIn = true
"filter.overdueTasks", "", filterOverdueOption.asJavafxProperty(), overdueFilterFxn, isBuiltIn = true
)
// ----------------------------
private val inProgressTodayFilterFxn: TaskFilterFxn = { _, child ->
@@ -91,7 +87,7 @@ object BuiltInFilters {
} != false
}
val inProgressTodayFilter = TaskFilter(
"filter.inProgressTodayTasks", "", filterInProgressTodayOption.asObservableValue(), inProgressTodayFilterFxn, isBuiltIn = true
"filter.inProgressTodayTasks", "", filterInProgressTodayOption.asJavafxProperty(), inProgressTodayFilterFxn, isBuiltIn = true
)

val allFilters: List<TaskFilter> get() = listOf(
@@ -152,7 +148,7 @@ class TaskFilterManager(val taskManager: TaskManager, val projectDatabase: Proje
})
}

fun createCustomFilter() = TaskFilter("", "", DefaultBooleanOption("", false),
fun createCustomFilter() = TaskFilter("", "",
filterFxn = { parent, child -> customFilterFxn(parent, child)},
isBuiltIn = false
)
@@ -187,10 +183,22 @@ class TaskFilterManager(val taskManager: TaskManager, val projectDatabase: Proje
internal var sync: ()->Unit = {}
}

private fun DefaultBooleanOption.asJavafxProperty() = SimpleBooleanProperty(this.value).also {
it.subscribe { oldValue, newValue ->
if (newValue != oldValue) {
this.setValue(newValue, it)
}
}
this.asObservableValue().addWatcher { evt ->
if (evt.newValue != evt.oldValue && evt.trigger != it) {
it.value = evt.newValue
}
}
}
private fun today() = CalendarFactory.createGanttCalendar(CalendarFactory.newCalendar().time)
private fun Task.endsToday() = this.end.displayValue == today()
private fun Task.endsBeforeToday() = this.end.displayValue < today()
private fun Task.runsToday() = today().let { this.end.displayValue >= it && this.start <= it }
val VOID_FILTER_FXN: TaskFilterFxn = { _, _ -> true }
val VOID_FILTER: TaskFilter = TaskFilter("filter.void", "", ObservableBoolean("", false), isBuiltIn = true, filterFxn = VOID_FILTER_FXN)
val VOID_FILTER: TaskFilter = TaskFilter("filter.void", "", isBuiltIn = true, filterFxn = VOID_FILTER_FXN)
private val LOGGER = GPLogger.create("TaskTable.Filters")
Original file line number Diff line number Diff line change
@@ -107,8 +107,8 @@ private void writeFilters(TransformerHandler handler, TaskFilterManager taskFilt
var attrs = new AttributesImpl();
addAttribute("title", filter.getTitle(), attrs);
addAttribute("description", filter.getDescription(), attrs);
addAttribute("isBuiltIn", filter.isBuiltIn(), attrs);
addAttribute("isEnabled", filter.isEnabledProperty().getValue(), attrs);
addAttribute("is-built-in", filter.isBuiltIn(), attrs);
addAttribute("is-enabled", filter.isEnabledProperty().getValue(), attrs);
try {
startElement("filter", attrs, handler);
if (filter.getExpression() != null) {