Skip to content

Commit 7cc4225

Browse files
author
Hanseter
committed
Add new control got a set of enum values
1 parent 648a3c3 commit 7cc4225

11 files changed

Lines changed: 268 additions & 29 deletions

File tree

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<modelVersion>4.0.0</modelVersion>
77
<groupId>com.github.hanseter</groupId>
88
<artifactId>json-properties-fx</artifactId>
9-
<version>2.0.0</version>
9+
<version>1.0.16</version>
1010

1111
<packaging>bundle</packaging>
1212
<name>JSON Properties Editor Fx</name>
@@ -123,7 +123,7 @@
123123
<dependency>
124124
<groupId>org.controlsfx</groupId>
125125
<artifactId>controlsfx</artifactId>
126-
<version>11.1.2</version>
126+
<version>11.2.1</version>
127127
</dependency>
128128
<dependency>
129129
<groupId>org.junit.jupiter</groupId>

src/main/kotlin/com/github/hanseter/json/editor/ControlFactory.kt

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.github.hanseter.json.editor.types.*
99
import com.github.hanseter.json.editor.types.FormattedIntegerModel.Companion.INT_FORMAT
1010
import com.github.hanseter.json.editor.util.EditorContext
1111
import org.everit.json.schema.*
12+
import org.json.JSONArray
1213
import org.slf4j.LoggerFactory
1314

1415

@@ -23,7 +24,7 @@ object ControlFactory {
2324
is ArraySchema -> createArrayControl(schema as EffectiveSchema<ArraySchema>, context)
2425
is BooleanSchema -> createBooleanControl(schema as EffectiveSchema<BooleanSchema>)
2526
is StringSchema -> createStringControl(schema as EffectiveSchema<StringSchema>, context)
26-
is NumberSchema -> createNumberControl(schema as EffectiveSchema<NumberSchema>,context)
27+
is NumberSchema -> createNumberControl(schema as EffectiveSchema<NumberSchema>, context)
2728
is EnumSchema -> createEnumControl(schema, actualSchema)
2829
is CombinedSchema -> createCombinedControl(
2930
schema as EffectiveSchema<CombinedSchema>,
@@ -39,8 +40,9 @@ object ControlFactory {
3940

4041
private fun createArrayControl(schema: EffectiveSchema<ArraySchema>, context: EditorContext) =
4142
when {
42-
schema.baseSchema.allItemSchema != null -> ArrayControl(
43-
ArrayModel(schema, schema.baseSchema.allItemSchema),
43+
schema.baseSchema.allItemSchema != null -> arrayControl(
44+
schema,
45+
schema.baseSchema.allItemSchema,
4446
context
4547
)
4648

@@ -54,6 +56,28 @@ object ControlFactory {
5456
else -> throw IllegalArgumentException("Only lists which contain the same type or tuples are supported. Check schema ${schema.baseSchema.schemaLocation}")
5557
}
5658

59+
private fun arrayControl(
60+
schema: EffectiveSchema<ArraySchema>,
61+
itemsSchema: Schema,
62+
context: EditorContext
63+
): TypeControl {
64+
if (schema.baseSchema.needsUniqueItems()) {
65+
getPotentialEnumSetSchema(itemsSchema)?.let {
66+
val model = EnumSetModel(schema, it)
67+
return RowBasedControl<JSONArray?>({ EnumSetControl(model, context) }, model)
68+
}
69+
}
70+
return ArrayControl(ArrayModel(schema, itemsSchema), context)
71+
}
72+
73+
private fun getPotentialEnumSetSchema(schema: Schema): EnumSchema? {
74+
if (schema is EnumSchema) return schema
75+
if (schema is CombinedSchema && schema.criterion == CombinedSchema.ALL_CRITERION && schema.synthetic) {
76+
return schema.subschemas.find { it is EnumSchema } as? EnumSchema
77+
}
78+
return null
79+
}
80+
5781
private fun createBooleanControl(schema: EffectiveSchema<BooleanSchema>) =
5882
RowBasedControl({ BooleanControl() }, BooleanModel(schema))
5983

@@ -65,7 +89,7 @@ object ControlFactory {
6589
ColorFormat.formatName -> RowBasedControl({ ColorControl() }, ColorModel(schema))
6690
IdReferenceFormat.formatName -> RowBasedControl(
6791
{ IdReferenceControl(schema, context) },
68-
IdReferenceModel(schema,context)
92+
IdReferenceModel(schema, context)
6993
)
7094

7195
LocalTimeFormat.formatName -> RowBasedControl(
@@ -74,15 +98,22 @@ object ControlFactory {
7498
)
7599

76100
"date" -> RowBasedControl({ DateControl() }, DateModel(schema))
77-
MultiLineFormat.FORMAT_NAME -> RowBasedControl({ MultiLineStringControl()}, StringModel(schema))
101+
MultiLineFormat.FORMAT_NAME -> RowBasedControl(
102+
{ MultiLineStringControl() },
103+
StringModel(schema)
104+
)
105+
78106
else -> RowBasedControl({ StringControl() }, StringModel(schema))
79107
}
80108

81-
private fun createNumberControl(schema: EffectiveSchema<NumberSchema>, context: EditorContext): TypeControl =
109+
private fun createNumberControl(
110+
schema: EffectiveSchema<NumberSchema>,
111+
context: EditorContext
112+
): TypeControl =
82113
if (schema.baseSchema.requiresInteger()) {
83114
if (schema.baseSchema.unprocessedProperties.keys.contains(INT_FORMAT)) {
84-
val model=FormattedIntegerModel(schema,context.decimalFormatSymbols)
85-
RowBasedControl({ FormattedIntegerControl(model) },model )
115+
val model = FormattedIntegerModel(schema, context.decimalFormatSymbols)
116+
RowBasedControl({ FormattedIntegerControl(model) }, model)
86117
} else RowBasedControl({ IntegerControl() }, IntegerModel(schema))
87118
} else {
88119
RowBasedControl({ DoubleControl(context.decimalFormatSymbols) }, DoubleModel(schema))

src/main/kotlin/com/github/hanseter/json/editor/JsonPropertiesEditor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ class JsonPropertiesEditor @JvmOverloads constructor(
389389
it.focusedProperty().addListener(focusListener)
390390
}
391391
updateControl(item)
392+
392393
item.registerChangeListener(changeListener)
393394
}
394395
}

src/main/kotlin/com/github/hanseter/json/editor/controls/ArrayControl.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import com.github.hanseter.json.editor.extensions.EffectiveSchema
55
import com.github.hanseter.json.editor.extensions.EffectiveSchemaInArray
66
import com.github.hanseter.json.editor.i18n.JsonPropertiesMl
77
import com.github.hanseter.json.editor.types.ArrayModel
8-
import com.github.hanseter.json.editor.types.PreviewString
98
import com.github.hanseter.json.editor.types.TypeModel
109
import com.github.hanseter.json.editor.util.*
1110
import org.everit.json.schema.ArraySchema
1211
import org.json.JSONArray
1312
import org.json.JSONObject
1413

1514

16-
class ArrayControl(override val model: ArrayModel, private val context: EditorContext) :
15+
class ArrayControl(
16+
override val model: ArrayModel,
17+
private val context: EditorContext
18+
) :
1719
TypeControl {
1820
override val childControls = mutableListOf<TypeControl>()
1921
private var subArray: BindableJsonArray? = null
@@ -27,12 +29,13 @@ class ArrayControl(override val model: ArrayModel, private val context: EditorCo
2729
override fun createLazyControl(): LazyControl = LazyArrayControl()
2830

2931
private inner class LazyArrayControl : LazyControl {
30-
override val control = TypeWithChildrenStatusControl(JsonPropertiesMl.bundle.getString("jsonEditor.control.array.create")) {
31-
model.value = JSONArray()
32-
valuesChanged()
33-
}.apply {
34-
isDisable = model.schema.readOnly
35-
}
32+
override val control =
33+
TypeWithChildrenStatusControl(JsonPropertiesMl.bundle.getString("jsonEditor.control.array.create")) {
34+
model.value = JSONArray()
35+
valuesChanged()
36+
}.apply {
37+
isDisable = model.schema.readOnly
38+
}
3639

3740
override fun updateDisplayedValue() {
3841
updateLabel()
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package com.github.hanseter.json.editor.controls
2+
3+
import com.github.hanseter.json.editor.types.EnumModel
4+
import com.github.hanseter.json.editor.types.EnumSetModel
5+
import com.github.hanseter.json.editor.util.EditorContext
6+
import javafx.beans.property.Property
7+
import javafx.beans.property.SimpleObjectProperty
8+
import javafx.beans.value.ChangeListener
9+
import javafx.collections.ListChangeListener
10+
import javafx.scene.control.Skin
11+
import javafx.util.StringConverter
12+
import org.controlsfx.control.CheckComboBox
13+
import org.json.JSONArray
14+
15+
class EnumSetControl(
16+
private val model: EnumSetModel,
17+
private val context: EditorContext
18+
) : ControlWithProperty<JSONArray?> {
19+
private val propListener = ChangeListener<JSONArray?> { _, _, new -> propChanged(new) }
20+
private val controlListener = ListChangeListener<Int> { c ->
21+
while (c.next()) {
22+
//mill changes
23+
}
24+
controlChanged(c.list)
25+
}
26+
27+
override val property: Property<JSONArray?> = SimpleObjectProperty<JSONArray?>(null).apply {
28+
addListener(propListener)
29+
}
30+
31+
override val control = object : CheckComboBox<String?>() {
32+
override fun createDefaultSkin(): Skin<*> {
33+
// controls fx resets the styleclasses when creating the skin. Make sure to preserve them.
34+
val oldClasses = styleClass.toList()
35+
val ret = super.createDefaultSkin()
36+
styleClass.addAll(oldClasses)
37+
return ret
38+
}
39+
}.apply { checkModel.checkedIndices.addListener(controlListener) }
40+
41+
init {
42+
43+
val enumDescriptions = EnumModel.getEnumDescriptions(model.schema, model.contentSchema)
44+
45+
control.items.setAll(
46+
model.contentSchema.possibleValuesAsList.filterNotNull().map { it.toString() })
47+
48+
// val cellFactory = Callback<ListView<String?>, ListCell<String?>> {
49+
// object : ListCell<String?>() {
50+
//
51+
// private val descLabel = Label().apply {
52+
// styleClass.add("enum-desc-label")
53+
//
54+
// val descClip = Rectangle().also {
55+
//
56+
// it.widthProperty().bind(widthProperty())
57+
// it.heightProperty().bind(heightProperty())
58+
//
59+
// // if the label is wider than the space available, its layoutX will be negative
60+
// // and it will be rendered outside the combobox cell (on the left side)
61+
// // with this, we clip it to be always contained within it
62+
// it.layoutXProperty().bind(Bindings.max(0, layoutXProperty().negate()))
63+
// it.layoutYProperty().bind(layoutYProperty())
64+
// }
65+
//
66+
// clip = descClip
67+
// }
68+
//
69+
// override fun updateItem(item: String?, empty: Boolean) {
70+
// super.updateItem(item, empty)
71+
//
72+
//
73+
//
74+
// if (item == null || empty) {
75+
// text = ""
76+
// graphic = null
77+
// } else {
78+
// text = item
79+
// contentDisplay = ContentDisplay.RIGHT
80+
// graphic = null
81+
//
82+
// enumDescriptions[item]?.let {
83+
// descLabel.text = "- $it"
84+
// graphic = descLabel
85+
// }
86+
// }
87+
// }
88+
//
89+
// }
90+
// }
91+
// control.buttonCell = cellFactory.call(null)
92+
// control.cellFactory = cellFactory
93+
94+
// Set the String converter because it's used by the search field.
95+
// It isn't used by the cells itself, so the converted value is never visible.
96+
control.converter = object : StringConverter<String?>() {
97+
override fun toString(obj: String?): String {
98+
return enumDescriptions[obj]?.let {
99+
"$obj $it"
100+
} ?: obj.orEmpty()
101+
}
102+
103+
override fun fromString(string: String?): String? = null
104+
}
105+
}
106+
107+
override fun previewNull(isNull: Boolean) {
108+
val wasNull = control.title != null
109+
control.title = if (isNull) TypeControl.NULL_PROMPT else null
110+
if (wasNull != isNull) {
111+
// force UI refresh.Unfortunately simply updating title doesn't do the trick :(
112+
pauseControlListener {
113+
val checks = control.checkModel.checkedIndices.toIntArray()
114+
val items = control.items.toList()
115+
control.items.clear()
116+
control.items.addAll(items)
117+
control.checkModel.checkIndices(*checks)
118+
}
119+
}
120+
}
121+
122+
private fun controlChanged(checkedIndices: List<Int>) {
123+
property.removeListener(propListener)
124+
val possibleValues = model.contentSchema.possibleValuesAsList.filterNotNull()
125+
property.value =
126+
JSONArray(checkedIndices.mapNotNull { possibleValues.getOrNull(it)?.toString() }
127+
.distinct())
128+
property.addListener(propListener)
129+
}
130+
131+
private fun propChanged(new: JSONArray?) {
132+
pauseControlListener {
133+
control.checkModel.clearChecks()
134+
val value = (new ?: model.defaultValue)?.toSet()
135+
if (value == null || model.rawValue == null) return
136+
137+
control.checkModel.checkIndices(*value.map { control.items.indexOf(it) }
138+
.filter { it > -1 }.toIntArray())
139+
}
140+
}
141+
142+
143+
private inline fun pauseControlListener(logic: () -> Unit) {
144+
control.checkModel.checkedIndices.removeListener(controlListener)
145+
try {
146+
logic()
147+
} finally {
148+
control.checkModel.checkedIndices.addListener(controlListener)
149+
}
150+
}
151+
152+
private fun JSONArray.toSet(): Set<String> =
153+
(0 until length()).mapNotNullTo(HashSet()) { optString(it, null) }
154+
155+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.github.hanseter.json.editor.types
2+
3+
class ArrayBaseModel {
4+
}

src/main/kotlin/com/github/hanseter/json/editor/types/ArrayModel.kt renamed to src/main/kotlin/com/github/hanseter/json/editor/types/ArrayModelBase.kt

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import com.github.hanseter.json.editor.extensions.EffectiveSchema
55
import com.github.hanseter.json.editor.i18n.JsonPropertiesMl
66
import com.github.hanseter.json.editor.util.BindableJsonType
77
import org.everit.json.schema.ArraySchema
8+
import org.everit.json.schema.EnumSchema
89
import org.everit.json.schema.Schema
910
import org.json.JSONArray
1011

11-
class ArrayModel(override val schema: EffectiveSchema<ArraySchema>, val contentSchema: Schema) :
12-
TypeModel<JSONArray?, SupportedType.ComplexType.ArrayType> {
13-
override val supportedType: SupportedType.ComplexType.ArrayType
14-
get() = SupportedType.ComplexType.ArrayType
12+
sealed class ArrayModelBase<S : Schema, T : SupportedType<JSONArray?>>(
13+
override val schema: EffectiveSchema<ArraySchema>,
14+
val contentSchema: S,
15+
override val supportedType: T
16+
) : TypeModel<JSONArray?, T> {
1517
override var bound: BindableJsonType? = null
1618
override val defaultValue: JSONArray?
1719
get() = (schema.defaultValue as? JSONArray)?.let { SchemaNormalizer.deepCopy(it) }
@@ -35,8 +37,23 @@ class ArrayModel(override val schema: EffectiveSchema<ArraySchema>, val contentS
3537
return if (value.length() == 1) {
3638
JsonPropertiesMl.bundle.getString("jsonEditor.control.array.element").format(1)
3739
} else {
38-
JsonPropertiesMl.bundle.getString("jsonEditor.control.array.elements").format(value.length())
40+
JsonPropertiesMl.bundle.getString("jsonEditor.control.array.elements")
41+
.format(value.length())
3942
}
4043
}
4144
}
42-
}
45+
}
46+
47+
class ArrayModel(schema: EffectiveSchema<ArraySchema>, contentSchema: Schema) :
48+
ArrayModelBase<Schema, SupportedType.ComplexType.ArrayType>(
49+
schema,
50+
contentSchema,
51+
SupportedType.ComplexType.ArrayType
52+
)
53+
54+
class EnumSetModel(schema: EffectiveSchema<ArraySchema>, contentSchema: EnumSchema) :
55+
ArrayModelBase<EnumSchema, SupportedType.SimpleType.EnumSetType>(
56+
schema,
57+
contentSchema,
58+
SupportedType.SimpleType.EnumSetType
59+
)

0 commit comments

Comments
 (0)