/*******************************************************************************
* Copyright (c) 2013, 2017 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.wizard;
import java.util.List;
import java.util.function.Supplier;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
import org.springsource.ide.eclipse.commons.livexp.ui.CommentSection;
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
import org.springsource.ide.eclipse.commons.ui.HtmlTooltip;
/**
* A section that shows a series of checkboxes in rows and columns.
* <p>
* The section has an optional label. If the label is provided then
* the section will be created as a labeled group with a bounding border
* around it. Otherwise a plain composite will be used to host the
* rows/columns of checkboxes.
*
* @author Kris De Volder
*/
public class CheckBoxesSection<T> extends WizardPageSection {
public static class CheckBoxModel<T> {
private final String label;
private final LiveVariable<Boolean> selection;
private final LiveExpression<Boolean> enablement;
private final T value;
private Supplier<String> tooltipHtml; //optional HTML tooltip supplier
public CheckBoxModel(String label, T value, LiveVariable<Boolean> selection, LiveExpression<Boolean> enablement) {
this.label = label;
this.value = value;
this.selection = selection;
this.enablement = enablement;
}
public String getLabel() {
return label;
}
public LiveVariable<Boolean> getSelection() {
return selection;
}
public LiveExpression<Boolean> getEnablement() {
return enablement;
}
public Supplier<String> getTooltipHtml() {
return tooltipHtml;
}
public void setTooltipHtml(Supplier<String> tooltipHtml) {
this.tooltipHtml = tooltipHtml;
}
public T getValue() {
return value;
}
@Override
public String toString() {
return "CheckBox("+label+", "+getSelection().getValue()+")" ;
}
}
private List<CheckBoxModel<T>> model;
private Composite composite;
private WizardPageSection[] subsections;
private int numCols;
private String label;
public CheckBoxesSection(IPageWithSections owner, List<CheckBoxModel<T>> model, String label) {
super(owner);
this.label = label;
this.model = model;
}
public CheckBoxesSection(IPageWithSections owner, List<CheckBoxModel<T>> model) {
this(owner, model, /*label*/null);
}
public CheckBoxesSection<T> columns(int howMany) {
Assert.isLegal(howMany>0);
this.numCols = howMany;
return this;
}
private static class CheckBox<T> extends WizardPageSection {
private Button cb;
private HtmlTooltip tooltip;
private CheckBoxModel<T> model;
private LiveVariable<Boolean> isVisible = new LiveVariable<>(true);
private ValueListener<Boolean> selectionListener;
private ValueListener<Boolean> enablementListener;
public CheckBox(IPageWithSections owner, CheckBoxModel<T> model) {
super(owner);
this.model = model;
}
@Override
public LiveExpression<ValidationResult> getValidator() {
return Validator.OK;
}
@Override
public void createContents(Composite page) {
if (page!=null && !page.isDisposed()) {
this.cb = new Button(page, SWT.CHECK);
cb.setText(model.getLabel());
Supplier<String> html = model.getTooltipHtml();
if (html != null) {
// Setup HTML tooltip and its content
this.tooltip = new HtmlTooltip(cb);
this.tooltip.setMaxSize(400, 400);
this.tooltip.setHtml(html);
}
model.getSelection().addListener(selectionListener = new ValueListener<Boolean>() {
public void gotValue(LiveExpression<Boolean> exp, Boolean value) {
// note: checkbox button may be null in cases where the
// model keeps changing in the container section and the selection
// listener is notified after the button was disposed
if (value!=null && cb != null && !cb.isDisposed()) {
cb.setSelection(value);
}
}
});
GridDataFactory.fillDefaults().grab(true, false).applyTo(cb);
cb.addSelectionListener(new SelectionListener() {
//@Override
public void widgetSelected(SelectionEvent e) {
handleSelection();
}
//@Override
public void widgetDefaultSelected(SelectionEvent e) {
handleSelection();
}
private void handleSelection() {
model.getSelection().setValue(cb.getSelection());
}
});
LiveExpression<Boolean> enablement = model.getEnablement();
if (enablement!=null) {
enablement.addListener(enablementListener = new ValueListener<Boolean>() {
public void gotValue(LiveExpression<Boolean> exp, Boolean value) {
if (value!=null) {
cb.setEnabled(value);
}
}
});
}
isVisible.addListener(new ValueListener<Boolean>() {
public void gotValue(LiveExpression<Boolean> exp, Boolean reveal) {
if (reveal!=null && cb !=null && !cb.isDisposed()) {
cb.setVisible(reveal);
GridData data = (GridData) cb.getLayoutData();
data.exclude = !reveal;
}
}
});
}
}
@Override
public void dispose() {
if (cb!=null && !cb.isDisposed()) {
cb.dispose();
cb = null;
}
if (selectionListener != null) {
model.getSelection().removeListener(selectionListener);
}
if (enablementListener != null) {
model.getEnablement().removeListener(enablementListener);
}
}
/**
* Apply filter and return whether this widget's visibility has changed as a result.
* @return Whether visibility of this widget changed.
*/
public boolean applyFilter(Filter<T> filter) {
boolean wasVisible = isVisible.getValue();
isVisible.setValue(filter.accept(model.getValue()));
boolean changed = wasVisible != isVisible.getValue();
return changed;
}
public boolean isVisible() {
return isVisible.getValue();
}
@Override
public String toString() {
return "CheckBox("+model.getLabel()+")";
}
}
protected GridLayout createLayout() {
return new GridLayout(numCols, true);
}
// @Override
// public LiveExpression<ValidationResult> getValidator() {
// return model.getValidator();
// }
@Override
public void createContents(Composite page) {
composite = createComposite(page);
GridLayout layout = createLayout();
layout.marginHeight = 0;
composite.setLayout(layout);
GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
createSubsections();
}
private void createSubsections() {
subsections = new WizardPageSection[Math.max(1, model.size())];
// GridData gd = (GridData) group.getLayoutData();
// boolean visible = checkboxes.length>0;
// gd.exclude = !visible;
if (model.isEmpty()) {
// don't leave section empty it looks ugly
subsections[0] = new CommentSection(owner, "No choices available");
subsections[0].createContents(composite);
}
for (int i = 0; i < model.size(); i++) {
subsections[i] = new CheckBox<>(owner, model.get(i));
subsections[i].createContents(composite);
}
}
protected Composite createComposite(Composite page) {
if (this.label!=null) {
Group comp = new Group(page, SWT.NONE);
comp.setText(label);
return comp;
} else {
return new Composite(page, SWT.NONE);
}
}
public boolean applyFilter(Filter<T> filter) {
if (subsections!=null) {
boolean visibilityChanged = false;
for (WizardPageSection subsection : subsections) {
if (subsection instanceof CheckBox) {
@SuppressWarnings("unchecked")
CheckBox<T> cb = (CheckBox<T>) subsection;
visibilityChanged |= cb.applyFilter(filter);
}
}
if (visibilityChanged) {
composite.layout(true);
}
return visibilityChanged;
}
return false;
}
public boolean hasVisible() {
if (subsections!=null) {
for (WizardPageSection s : subsections) {
if (s instanceof CheckBox) {
@SuppressWarnings("unchecked")
CheckBox<T> cb = ((CheckBox<T>) s);
if (cb.isVisible()) {
return true;
}
}
}
}
return false;
}
boolean isCreated() {
return composite != null;
}
@Override
public void dispose() {
if (subsections != null) {
for (WizardPageSection subsection : subsections) {
subsection.dispose();
}
}
super.dispose();
}
public void setModel(List<CheckBoxModel<T>> model) {
dispose();
this.model = model;
createSubsections();
composite.layout();
}
}