package org.ovirt.engine.ui.webadmin.uicommon.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.ovirt.engine.ui.common.uicommon.model.TreeNodeModel;
import org.ovirt.engine.ui.common.utils.ElementIdUtils;
import org.ovirt.engine.ui.common.widget.tree.TreeModelWithElementId;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.CheckboxCell;
import com.google.gwt.cell.client.CompositeCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.HasCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.DOM;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.DefaultSelectionEventManager;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.MultiSelectionModel;
/**
* A TreeView Model for {@link TreeNodeModel} Nodes
*/
public class ModelListTreeViewModel<T, M extends TreeNodeModel<T, M>> implements TreeModelWithElementId {
private final class NodeSelectionHandler implements SelectionHandler<M> {
private final HasData<M> display;
private NodeSelectionHandler(HasData<M> display) {
this.display = display;
}
@Override
public void onSelection(SelectionEvent<M> event) {
M selectedItem = event.getSelectedItem();
display.getSelectionModel().setSelected(selectedItem,
selectedItem.getSelected());
}
}
private final class CellLabel extends TextColumn<M> {
@Override
public String getValue(M object) {
return object.getName();
}
}
private final class CheckboxColumn implements HasCell<M, Boolean> {
private final CheckboxCell cell = new ExCheckboxCell(true, false);
@Override
public Cell<Boolean> getCell() {
return cell;
}
@Override
public FieldUpdater<M, Boolean> getFieldUpdater() {
return null;
}
@Override
public Boolean getValue(M object) {
return selectionModel.isSelected(object);
}
}
interface ExCheckboxCellTemplate extends SafeHtmlTemplates {
/**
* An html string representation of a checked input box.
*/
@Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\" checked/>")
SafeHtml inputChecked(String elementId);
/**
* An html string representation of an unchecked input box.
*/
@Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\"/>")
SafeHtml inputUnchecked(String elementId);
/**
* An html string representation of a disabled checked input box.
*/
@Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\" checked disabled/>")
SafeHtml inputCheckedDisabled(String elementId);
/**
* An html string representation of a disabled unchecked input box.
*/
@Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\" disabled/>")
SafeHtml inputUncheckedDisabled(String elementId);
}
/**
* A {@link CheckboxCell} that can be disabled
*/
private final class ExCheckboxCell extends CheckboxCell {
private final ExCheckboxCellTemplate template = GWT.create(ExCheckboxCellTemplate.class);
private ExCheckboxCell(boolean dependsOnSelection, boolean handlesSelection) {
super(dependsOnSelection, handlesSelection);
}
@SuppressWarnings("unchecked")
@Override
public void render(Context context, Boolean value, SafeHtmlBuilder sb) {
// TODO semi-checked checkbox (null value)
// value = ((SimpleSelectionTreeNodeModel) context.getKey()).getIsSelectedNullable();
Object key = context.getKey();
Boolean viewData = getViewData(key);
M nodeModel = (M) key;
String elementId = ElementIdUtils.createTreeCellElementId(
elementIdPrefix, nodeModel, roots);
if (viewData != null && viewData.equals(value)) {
clearViewData(key);
viewData = null;
}
if (value != null && ((viewData != null) ? viewData : value)) {
// Checked state
sb.append(nodeModel.isEditable() ? template.inputChecked(elementId)
: template.inputCheckedDisabled(elementId));
} else {
// Unchecked state
sb.append(nodeModel.isEditable() ? template.inputUnchecked(elementId)
: template.inputUncheckedDisabled(elementId));
}
}
}
private final AsyncDataProvider<M> asyncTreeDataProvider;
private List<M> roots;
private final CompositeCell<M> compositeCell;
private final MultiSelectionModel<M> selectionModel = new MultiSelectionModel<>();
private NodeSelectionHandler nodeSelectionHandler;
private final HandlerRegistration selectionModelChangeHandlerReg;
private final List<HandlerRegistration> nodeModelSelectionHandlerRegList = new ArrayList<>();
private String elementIdPrefix = DOM.createUniqueId();
public ModelListTreeViewModel() {
List<HasCell<M, ?>> cells = new ArrayList<>();
cells.add(new CheckboxColumn());
cells.add(new CellLabel());
this.compositeCell = new CompositeCell<>(cells);
asyncTreeDataProvider = new AsyncDataProvider<M>() {
@Override
protected void onRangeChanged(HasData<M> display) {
// no-op
}
@Override
protected void updateRowData(final HasData<M> display, int start, List<M> values) {
super.updateRowData(display, start, values);
for (M model : values) {
updateSelection(model, display);
}
}
};
// Drive selection
selectionModelChangeHandlerReg = selectionModel.addSelectionChangeHandler(event -> {
Set<M> selectedSet = selectionModel.getSelectedSet();
HashSet<TreeNodeModel<?, ?>> removedSet = new HashSet<>();
updateSelectionSets(selectedSet, removedSet, roots);
for (M toSelect : selectedSet) {
toSelect.setSelected(true);
}
for (TreeNodeModel<?, ?> toDeselect : removedSet) {
toDeselect.setSelected(false);
}
});
}
/**
* Recursively determine the set of changed nodes.
* @param selectedSet The set of selected nodes.
* @param removedSet The set of removed nodes.
* @param nodes The list of nodes to check against.
*/
private void updateSelectionSets(Set<M> selectedSet, Set<TreeNodeModel<?, ?>> removedSet, List<M> nodes) {
for (TreeNodeModel<T, M> node : nodes) {
if (!selectedSet.contains(node) && node.getSelected()) {
removedSet.add(node);
}
updateSelectionSets(selectedSet, removedSet, node.getChildren());
}
}
public void updateSelection(M model, final HasData<M> display) {
// Add Selection Listener
if (nodeSelectionHandler == null) {
nodeSelectionHandler = new NodeSelectionHandler(display);
}
nodeModelSelectionHandlerRegList.add(model.addSelectionHandler(nodeSelectionHandler));
// show value
display.getSelectionModel().setSelected(model, model.getSelected());
for (int i = 0; i < model.getChildren().size(); i++) {
updateSelection(model.getChildren().get(i), display);
}
}
public void removeHandlers() {
selectionModelChangeHandlerReg.removeHandler();
for (HandlerRegistration reg : nodeModelSelectionHandlerRegList) {
reg.removeHandler();
}
}
public AsyncDataProvider<M> getAsyncTreeDataProvider() {
return asyncTreeDataProvider;
}
@SuppressWarnings("unchecked")
@Override
public <N> NodeInfo<?> getNodeInfo(N value) {
M model = (M) value;
if (value == null) {
// root node
return new DefaultNodeInfo<>(asyncTreeDataProvider,
compositeCell, selectionModel,
DefaultSelectionEventManager.<M> createCheckboxManager(),
null);
} else {
// child nodes
return new DefaultNodeInfo<>(new ListDataProvider<>(model.getChildren()),
compositeCell, selectionModel,
DefaultSelectionEventManager.<M> createCheckboxManager(),
null);
}
}
@SuppressWarnings("unchecked")
@Override
public boolean isLeaf(Object value) {
M model = (M) value;
return model != null && model.getChildren() != null && model.getChildren().size() == 0;
}
/**
* Set the Root list- required to sync the Model Tree correctly
*/
public void setRoots(List<M> arrayList) {
this.roots = arrayList;
}
@Override
public void setElementIdPrefix(String elementIdPrefix) {
this.elementIdPrefix = elementIdPrefix;
}
}