/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.boot.dash.views.sections;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.springframework.ide.eclipse.boot.dash.livexp.DelegatingLiveSet;
import org.springframework.ide.eclipse.boot.dash.livexp.MultiSelection;
import org.springframework.ide.eclipse.boot.dash.livexp.MultiSelectionSource;
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
import org.springsource.ide.eclipse.commons.livexp.core.ObservableSet;
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
import org.springsource.ide.eclipse.commons.livexp.ui.Disposable;
import org.springsource.ide.eclipse.commons.livexp.ui.IPageSection;
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
import org.springsource.ide.eclipse.commons.livexp.ui.PageSection;
import org.springsource.ide.eclipse.commons.livexp.ui.util.ReflowUtil;
public class DynamicCompositeSection<M> extends PageSection implements MultiSelectionSource, Disposable {
public static interface SectionFactory<M> {
IPageSection create(M model);
}
/**
* A dynamically changing collection of models. When this changes the DynamicComposite will react by
* creating or deleting sections corresponding to the corresponding added/deleted models.
*/
private LiveExpression<Set<M>> models;
/**
* Keeps track of sections per model.
*/
private Map<M, SubSection> sectionsMap = new LinkedHashMap<>();
/**
* Keeps track of the selected elements
*/
private DelegatingLiveSet<?> elements;
private MultiSelection<?> selection; //Because of the dynamic nature, we can't really know what kinds of selections will get added
// So we must assume any kinds of object may appear.
private Composite composite = null; //Set once UI is created
private SectionFactory<M> sectionFactory;
private ValueListener<Set<M>> modelListener = new ValueListener<Set<M>>() {
public void gotValue(LiveExpression<Set<M>> exp, Set<M> value) {
updateSections();
}
};
private Class<?> selectionType = Object.class;
private class SubSection {
Collection<Control> ui;
IPageSection section;
}
public <T> DynamicCompositeSection(IPageWithSections owner, LiveExpression<Set<M>> models, SectionFactory<M> sectionFactory, Class<T> selectionType) {
super(owner);
this.models = models;
DelegatingLiveSet<T> _elements = new DelegatingLiveSet<>();
this.elements = _elements;
this.sectionFactory = sectionFactory;
this.selectionType = selectionType;
this.selection = MultiSelection.from(selectionType, _elements);
}
@Override
public MultiSelection<?> getSelection() {
return selection;
}
@Override
public LiveExpression<ValidationResult> getValidator() {
return OK_VALIDATOR;
}
@Override
public void createContents(Composite page) {
// composite = new Composite(page, SWT.NONE);
// Layout l = new GridLayout();
// composite.setLayout(l);
// GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
composite = page;
models.addListener(modelListener);
}
/**
* Called when we need to update the sections based on the models
*/
private synchronized void updateSections() {
Set<M> currentModels = models.getValue();
//Missing: current models for which we have no section
Set<M> missing = new LinkedHashSet<>(currentModels);
missing.removeAll(sectionsMap.keySet());
//Extra: current sections for which there is no more model
Set<M> extra = new HashSet<>();
extra.addAll(sectionsMap.keySet());
extra.removeAll(currentModels);
for (M m : extra) {
deleteSectionFor(m);
}
for (M m : missing) {
createSectionFor(m);
}
boolean dirty = !missing.isEmpty() || !extra.isEmpty();
if (dirty) {
ReflowUtil.reflow(owner, composite);
updateSelectionDelegate();
}
}
@SuppressWarnings("unchecked")
private <T> void updateSelectionDelegate() {
Class<T> selectionType = (Class<T>) this.selectionType;
MultiSelection<T> newSelection = MultiSelection.empty(selectionType);
for (SubSection s : sectionsMap.values()) {
if (s.section instanceof MultiSelectionSource) {
newSelection = combineSelections(
newSelection,
((MultiSelectionSource)s.section).getSelection().filter(selectionType)
);
}
}
@SuppressWarnings("rawtypes")
ObservableSet newElements = newSelection.getElements();
elements.setDelegate(newElements);
}
protected <T> MultiSelection<T> combineSelections(MultiSelection<T> a, MultiSelection<T> b) {
return MultiSelection.union(a, b);
}
private void createSectionFor(M m) {
Set<Control> oldWidgets = new HashSet<>(Arrays.asList(composite.getChildren()));
SubSection s = new SubSection();
s.section = sectionFactory.create(m);
s.section.createContents(composite);
s.ui = new HashSet<>(Arrays.asList(composite.getChildren()));
s.ui.removeAll(oldWidgets);
sectionsMap.put(m, s);
}
private void deleteSectionFor(M m) {
SubSection s = sectionsMap.get(m);
sectionsMap.remove(m);
if (s.section instanceof Disposable) {
((Disposable) s.section).dispose();
}
if (s.ui!=null) {
for (Control widget : s.ui) {
widget.dispose();
}
}
}
@Override
public void dispose() {
if (modelListener!=null) {
models.removeListener(modelListener);
}
if (sectionsMap!=null) {
for (SubSection s : sectionsMap.values()) {
if (s.section instanceof Disposable) {
((Disposable)s.section).dispose();
}
}
}
}
}