TuplePropertyComboModel.java
package swingtree;
import org.jspecify.annotations.Nullable;
import sprouts.From;
import sprouts.Tuple;
import sprouts.Val;
import sprouts.Var;
import sprouts.Viewable;
/**
* A combo box model whose available options are modelled by a {@link Tuple}
* based property. Note that the UI thread never accesses the property itself!
* Because a {@link Tuple} is deeply immutable, the two threads can simply
* publish their state to each other: this model maintains a UI thread owned
* snapshot of the tuple, which is refreshed through the property change events
* of the item property, and conversely, an item edited by the user in the view
* is first applied to this snapshot and then handed over to the application
* thread, which writes it into the property.
* This is the same threading convention that the selection state
* of the {@link AbstractComboModel} follows.
*/
final class TuplePropertyComboModel<E extends @Nullable Object> extends AbstractComboModel<E>
{
private final Val<Tuple<E>> _items;
private final Viewable<Tuple<E>> _itemsView; // A strong reference keeps the (weakly parented) view and its change listener alive.
private volatile @Nullable Tuple<E> _itemsSnapshot; // The UI thread owned copy of the state of `_items`.
private final boolean _mutable;
TuplePropertyComboModel(Var<E> selection, Val<Tuple<E>> items ) {
this( selection, items, false );
}
TuplePropertyComboModel(Var<E> selection, Var<Tuple<E>> items ) {
this( selection, items, true );
}
private TuplePropertyComboModel(Var<E> selection, Val<Tuple<E>> items, boolean mutable ) {
super(selection);
_items = items;
_itemsSnapshot = items.orElseNull();
_mutable = mutable;
_selectedIndex = _indexOf(_getSelectedItemSafely());
_itemsView = _items.view();
_itemsView.onChange(From.ALL, it -> {
@Nullable Tuple<E> newItems = it.currentValue().orElseNull();
_publishToUIThread(() -> {
_itemsSnapshot = newItems;
updateSelectedIndex();
fireListeners();
});
});
}
@Override
public AbstractComboModel<E> withVar( Var<E> newVar ) {
return new TuplePropertyComboModel<>(newVar, _items, _mutable);
}
@Override protected void setAt(int index, @Nullable E element) {
if ( _mutable && _items instanceof Var ) {
Tuple<E> snapshot = _itemsSnapshot;
if ( snapshot != null && index >= 0 && index < snapshot.size() )
_itemsSnapshot = snapshot.setAt(index, NullUtil.fakeNonNull(element));
_publishToAppThread(() ->
((Var<Tuple<E>>)_items).update(From.VIEW, tuple -> {
if ( index < 0 || index >= tuple.size() )
return tuple; // The tuple changed in the meantime; the edit no longer applies.
return tuple.setAt(index, NullUtil.fakeNonNull(element));
})
);
}
}
@Override public int getSize() {
Tuple<E> snapshot = _itemsSnapshot;
return snapshot == null ? 0 : snapshot.size();
}
@Override public @Nullable E getElementAt( int index ) {
Tuple<E> snapshot = _itemsSnapshot;
if ( snapshot == null || index < 0 || index >= snapshot.size() )
return null;
return snapshot.get(index);
}
}