Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import dev.ikm.komet.framework.events.EntityVersionChangeEvent;
import dev.ikm.komet.framework.events.EvtBusFactory;
import dev.ikm.tinkar.collection.ConcurrentReferenceHashMap;
import dev.ikm.tinkar.common.id.PublicId;
import dev.ikm.tinkar.common.service.PrimitiveData;
import dev.ikm.tinkar.common.util.broadcast.Subscriber;
import dev.ikm.tinkar.component.FieldDataType;
Expand All @@ -35,9 +36,8 @@
import dev.ikm.tinkar.entity.StampEntity;
import dev.ikm.tinkar.entity.StampRecord;
import javafx.application.Platform;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.map.ImmutableMap;
Expand Down Expand Up @@ -67,7 +67,7 @@ public abstract sealed class ObservableEntity<O extends ObservableVersion<V>, V
Entity.provider().addSubscriberWithWeakReference(ENTITY_CHANGE_SUBSCRIBER);
}

final SimpleListProperty<O> versionProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
final SimpleMapProperty<Integer, O> versionPropertyMap = new SimpleMapProperty<>(FXCollections.observableHashMap());

final private AtomicReference<Entity<V>> entityReference;

Expand All @@ -77,9 +77,10 @@ public abstract sealed class ObservableEntity<O extends ObservableVersion<V>, V
* @param analogue the entity record
* @param newVersionRecord entity version record
*/
public void saveToDB(Entity<?> analogue, EntityVersion newVersionRecord ) {
public void saveToDB(Entity<?> analogue, EntityVersion newVersionRecord , EntityVersion oldVersionRecord) {
Entity.provider().putEntity(analogue);
versionProperty.add(wrap((V)newVersionRecord));
versionPropertyMap.remove(oldVersionRecord.stamp().nid());
versionPropertyMap.put(newVersionRecord.stamp().nid(), wrap((V)newVersionRecord));
EvtBusFactory.getDefaultEvtBus()
.publish(VERSION_CHANGED_TOPIC, new EntityVersionChangeEvent(this, VERSION_UPDATED, newVersionRecord));
}
Expand All @@ -99,7 +100,7 @@ public void saveToDB(Entity<?> analogue, EntityVersion newVersionRecord ) {

this.entityReference = new AtomicReference<>(entityClone);
for (V version : entity.versions()) {
versionProperty.add(wrap(version));
versionPropertyMap.put(version.stamp().nid(), wrap(version));
}
}

Expand All @@ -115,22 +116,24 @@ ObservableEntitySnapshot<OE, OV, EV> getSnapshot(int nid, ViewCalculator calcula
public abstract ObservableEntitySnapshot<?,?,?> getSnapshot(ViewCalculator calculator);

public static <OE extends ObservableEntity> OE get(Entity<? extends EntityVersion> entity) {
if (entity instanceof ObservableEntity) {
ObservableEntity observableEntity = (ObservableEntity) entity;
updateVersions(observableEntity.entity(), observableEntity);
return (OE) entity;

ObservableEntity observableEntity = null;
if (!(entity instanceof ObservableEntity)) {
observableEntity = SINGLETONS.computeIfAbsent(entity.nid(), publicId ->
switch (entity) {
case ConceptEntity conceptEntity -> new ObservableConcept(conceptEntity);
case PatternEntity patternEntity -> new ObservablePattern(patternEntity);
case SemanticEntity semanticEntity -> new ObservableSemantic(semanticEntity);
case StampEntity stampEntity -> new ObservableStamp(stampEntity);
default -> throw new UnsupportedOperationException("Can't handle: " + entity);
});
} else {
observableEntity = (ObservableEntity) entity;
}

ObservableEntity observableEntity = SINGLETONS.computeIfAbsent(entity.nid(), publicId ->
switch (entity) {
case ConceptEntity conceptEntity -> new ObservableConcept(conceptEntity);
case PatternEntity patternEntity -> new ObservablePattern(patternEntity);
case SemanticEntity semanticEntity -> new ObservableSemantic(semanticEntity);
case StampEntity stampEntity -> new ObservableStamp(stampEntity);
default -> throw new UnsupportedOperationException("Can't handle: " + entity);
});
if (!Platform.isFxApplicationThread()) {
Platform.runLater(() -> updateVersions(entity, observableEntity));
ObservableEntity finalObservableEntity = observableEntity;
Platform.runLater(() -> updateVersions(entity, finalObservableEntity));
} else {
updateVersions(entity, observableEntity);
}
Expand All @@ -142,15 +145,25 @@ public static <OE extends ObservableEntity> OE get(Entity<? extends EntityVersio
* @param entity
* @param observableEntity
*/
public static void updateVersions(Entity<? extends EntityVersion> entity, ObservableEntity observableEntity) {
if (!((Entity) observableEntity.entityReference.get()).versions().equals(entity.versions())) {
observableEntity.entityReference.set(entity);
observableEntity.versionProperty.clear();
for (EntityVersion version : entity.versions().stream().sorted((v1, v2) ->
Long.compare(v1.stamp().time(), v2.stamp().time())).toList()) {
observableEntity.versionProperty.add(observableEntity.wrap(version));
private synchronized static void updateVersions(Entity<? extends EntityVersion> entity, ObservableEntity observableEntity) {
boolean updateEntityReference = false;
for (EntityVersion version : entity.versions().stream().sorted((v1, v2) ->
Long.compare(v1.stamp().time(), v2.stamp().time())).toList()) {
boolean versionPresent = observableEntity.versionPropertyMap.get().values().stream().anyMatch(obj -> {
if (obj instanceof ObservableVersion<?> observableVersion){
return observableVersion.stamp().nid() == version.stamp().nid();
}
return false;
});

if(!versionPresent){
observableEntity.versionPropertyMap.put(version.stamp().nid(), observableEntity.wrap(version));
updateEntityReference = true;
}
}
if (updateEntityReference) {
observableEntity.entityReference.set(entity);
}
}

public static <OE extends ObservableEntity> OE get(int nid) {
Expand All @@ -161,13 +174,13 @@ protected Entity<V> entity() {
return entityReference.get();
}

public ObservableList<O> versionProperty() {
return versionProperty;
public SimpleMapProperty<Integer, O> versionPropertyMap() {
return versionPropertyMap;
}

@Override
public ImmutableList<O> versions() {
return Lists.immutable.ofAll(versionProperty);
return Lists.immutable.ofAll(versionPropertyMap.values());
}

@Override
Expand Down Expand Up @@ -210,15 +223,13 @@ public Iterable<ObservableSemantic> getObservableSemanticList() {
}

private class EntityChangeSubscriber implements Subscriber<Integer> {

@Override
public void onNext(Integer nid) {
// Do nothing with item, but request another...
if (SINGLETONS.containsKey(PrimitiveData.publicId(nid))) {
Platform.runLater(() -> {
get(Entity.getFast(nid));
});

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public ObservableEntitySnapshot(ViewCalculator viewCalculator, OE entity) {
MutableList<OV> uncommittedVersions = Lists.mutable.empty();
MutableList<OV> historicVersions = Lists.mutable.empty();

for (OV version : this.observableEntity.versionProperty()) {
for (OV version : this.observableEntity.versionPropertyMap().values()) {
processedVersions.add(version);
if (version.uncommitted()) {
uncommittedVersions.add(version);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ public final class ObservableSemantic
@Override
protected ObservableSemanticVersion wrap(SemanticVersionRecord version) {
ObservableSemanticVersion observableSemanticVersion = new ObservableSemanticVersion(version);
observableSemanticVersion.versionProperty.addListener((observable) -> {
updateEntity(observableSemanticVersion.versionProperty.get());
observableSemanticVersion.versionProperty.addListener((observable, oldValue, newValue) -> {
updateEntity(newValue, oldValue);
});
return observableSemanticVersion;
}

private void updateEntity(SemanticVersionRecord newSemanticVersionRecord) {
private void updateEntity(SemanticVersionRecord newSemanticVersionRecord, SemanticVersionRecord oldSemanticVersionRecord) {
SemanticRecord semantic = Entity.getFast(newSemanticVersionRecord.nid());
SemanticRecord analogue = semantic.with(newSemanticVersionRecord).build();
saveToDB(analogue, newSemanticVersionRecord);
saveToDB(analogue, newSemanticVersionRecord, oldSemanticVersionRecord);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,10 @@ public final void setTitle(String value) {
private final ObjectProperty<EntityProxy> entityProperty = new SimpleObjectProperty<>(this, "entity", null);
public final ObjectProperty<EntityProxy> entityProperty() { return entityProperty; }
public final EntityProxy getEntity() {
if (((Object)entityProperty.get()) instanceof ConceptRecord) {
ConceptRecord conceptRecord = (ConceptRecord) (Object) entityProperty.get();
entityProperty.set(Entity.getFast(conceptRecord.nid()).toProxy());
}
return entityProperty.get();
return entityProperty.get() == null ? null : EntityProxy.make(entityProperty.get().nid());
}
public final void setEntity(EntityProxy value) {
if (((Object)value) instanceof ConceptRecord) {
ConceptRecord conceptRecord = (ConceptRecord) (Object) value;
entityProperty.set(Entity.getFast(conceptRecord.nid()).toProxy());
}
entityProperty.set(value);
entityProperty.set(value == null ? null : EntityProxy.make(value.nid()));
}

// -- type ahead completer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dev.ikm.komet.kview.controls.skin;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
import javafx.scene.control.TextField;
import javafx.util.Duration;

public abstract class KLDebounceControlSkin<C extends Control> extends SkinBase<C> {

protected final TextField textField;
/**
* Constructor for all SkinBase instances.
*
* @param control The control for which this Skin should attach to.
*/
protected KLDebounceControlSkin(C control) {
super(control);
this.textField = new TextField();
Timeline timeline = new Timeline();
KeyFrame keyFrame1 = new KeyFrame(Duration.millis(3000), (evt) -> {});

timeline.getKeyFrames().addAll(keyFrame1);

timeline.setOnFinished((evt) -> {
updateValueProperty();
});

textField.setOnKeyPressed( _ -> {
timeline.playFromStart();
});

textField.focusedProperty().subscribe( () -> {
if (!textField.isFocused()) {
timeline.stop();
updateValueProperty();
}
});

}

protected abstract void updateValueProperty();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import javafx.animation.PauseTransition;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.Subscription;

Expand All @@ -15,17 +13,16 @@
/**
* Default skin implementation for the {@link KLFloatControl} control
*/
public class KLFloatControlSkin extends SkinBase<KLFloatControl> {
public class KLFloatControlSkin extends KLDebounceControlSkin<KLFloatControl> {

private static final Pattern NUMERICAL_PATTERN = Pattern.compile("^[+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?)$");
private static final ResourceBundle resources = ResourceBundle.getBundle("dev.ikm.komet.kview.controls.float-control");

private final Label titleLabel;
private final TextField textField;

private final Label errorLabel;

private Subscription subscription;
private boolean textChangedViaKeyEvent;

/**
* Creates a new KLFloatControlSkin instance, installing the necessary child
Expand All @@ -41,7 +38,6 @@ public KLFloatControlSkin(KLFloatControl control) {
titleLabel.textProperty().bind(control.titleProperty());
titleLabel.getStyleClass().add("editable-title-label");

textField = new TextField();
textField.promptTextProperty().bind(control.promptTextProperty());
textField.getStyleClass().add("text-field");

Expand Down Expand Up @@ -127,27 +123,7 @@ public KLFloatControlSkin(KLFloatControl control) {
}));

// value was set externally
subscription = control.valueProperty().subscribe(nv -> {
if (!textChangedViaKeyEvent) {
textField.setText(nv == null ? null : nv.toString());
}
});
subscription = subscription.and(textField.textProperty().subscribe(nv -> {
textChangedViaKeyEvent = true;
if (nv == null || nv.isEmpty()) {
// When new text is null or empty, reset control's value
control.setValue(null);
} else if (!("-".equals(nv) || "+".equals(nv) || endsWithExponent(nv) || endsWithSignedExponent(nv))) {
try {
// only set control's value when it is a valid number
float value = Float.parseFloat(nv);
control.setValue(value);
} catch (NumberFormatException e) {
// ignore, and keep control with its old value
}
}
textChangedViaKeyEvent = false;
}));
subscription = control.valueProperty().subscribe(nv -> textField.setText(nv == null ? null : nv.toString()));

final PauseTransition pauseTransition = new PauseTransition(KLIntegerControlSkin.ERROR_DURATION);
pauseTransition.setOnFinished(f -> {
Expand All @@ -165,6 +141,24 @@ public KLFloatControlSkin(KLFloatControl control) {
}));
}

@Override
protected void updateValueProperty() {
String nv = textField.getText();
KLFloatControl control = getSkinnable();
if (nv == null || nv.isEmpty()) {
// When new text is null or empty, reset control's value
control.setValue(null);
} else if (!("-".equals(nv) || "+".equals(nv) || endsWithExponent(nv) || endsWithSignedExponent(nv))) {
try {
// only set control's value when it is a valid number
float value = Float.parseFloat(nv);
control.setValue(value);
} catch (NumberFormatException e) {
// ignore, and keep control with its old value
}
}
}

/** {@inheritDoc} */
@Override
public void dispose() {
Expand Down
Loading