package org.ovirt.engine.ui.common.widget.editor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.ovirt.engine.core.common.utils.ObjectUtils;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.text.shared.Renderer;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasConstrainedValue;
/**
* The CheckboxGroup Widget is used to group together a set of Checkbox buttons. Any number of checkboxes can be
* checked/set at any point in time. Pushing/Clicking any checkbox in the group toggles its state.
*/
public class CheckBoxGroup<T> extends Composite implements TakesValue<List<T>>, HasConstrainedValue<List<T>> {
private final Map<T, CheckBox> checkBoxes = new LinkedHashMap<>();
private static Resources RESOURCES = GWT.create(Resources.class);
private final FlowPanel wrapperPanel = new FlowPanel();
private final CheckBoxGroupCss style;
private boolean enabled = true;
Renderer<T> renderer;
int tabIndex;
public interface Resources extends ClientBundle {
@Source("org/ovirt/engine/ui/common/css/CheckBoxGroup.css")
CheckBoxGroupCss checkBoxGroupCss();
}
/**
* CheckBoxGroup construcor.
* @param renderer
* to render the values passed to ListModel's setItems and hence setAcceptableValues
*/
public CheckBoxGroup(Renderer<T> renderer) {
this.renderer = renderer;
style = RESOURCES.checkBoxGroupCss();
style.ensureInjected();
initWidget(wrapperPanel);
}
private void addCheckBox(T checkBoxValue) {
String checkBoxLabel = renderer.render(checkBoxValue);
if (checkBoxLabel == null) {
throw new IllegalArgumentException("null value is not permited"); //$NON-NLS-1$
}
final CheckBox newCheckBox = buildCheckbox(checkBoxValue);
checkBoxes.put(checkBoxValue, newCheckBox);
}
private CheckBox buildCheckbox(final T checkBoxValue) {
final CheckBox newCheckBox = new CheckBox(SafeHtmlUtils.fromString(renderer.render(checkBoxValue)));
newCheckBox.setValue(false);
newCheckBox.setStyleName(style.checkBox());
newCheckBox.addClickHandler(event -> {
// ValueChangeEvent fired to notify the mapped ListModel about the new Selection/deselection.
ValueChangeEvent.fire(CheckBoxGroup.this, getValue());
});
return newCheckBox;
}
/**
* Clear All checkBoxes' selection in the group
*/
public void clearAllSelections() {
for (Entry<T, CheckBox> currentcheckBoxValue : checkBoxes.entrySet()) {
currentcheckBoxValue.getValue().setValue(false);
}
}
public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) {
return addDomHandler(handler, KeyUpEvent.getType());
}
public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
return addDomHandler(handler, KeyDownEvent.getType());
}
public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
return addDomHandler(handler, KeyPressEvent.getType());
}
public boolean isEnabled() {
return enabled;
}
/**
* Enable/disable all checkboxes
* @param enabled
* boolean whether to enable/disable all checkboxes
*/
public void setEnabled(boolean enabled) {
for(Entry<T, CheckBox> currentValue : checkBoxes.entrySet()) {
currentValue.getValue().setEnabled(enabled);
}
}
public int getTabIndex() {
return tabIndex;
}
public void setTabIndex(int index) {
tabIndex = index;
}
/**
* When the mapped ListModel does a setSelectedItem, this is invoked. This method sets checked, the checkboxes
* corresponding to the list passed to it.
* @param value
* list of checkboxes to set checked.
* @param fireEvents
* whether to fire ValueChangeEvent
*/
@Override
public void setValue(List<T> value, boolean fireEvents) {
List<T> selectedItems = getValue();
if (value == selectedItems || ObjectUtils.haveSameElements(selectedItems, value)) {
return;
}
clearAllSelections();
if(value == null){
return;
}
for (T currentvalue : value) {
if (checkBoxes.containsKey(currentvalue)) {
checkBoxes.get(currentvalue).setValue(true);
}
}
if (fireEvents) {
ValueChangeEvent.fire(this, value);
}
}
@Override
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<List<T>> handler) {
return addHandler(handler, ValueChangeEvent.getType());
}
/**
* Api to add list of CheckBoxes to the CheckBoxGroup. This is invoked by the mapped ListModel's setItems.
* @param values
* list of values for which checkboxes are to be created in the group.
*/
@Override
public void setAcceptableValues(Collection<List<T>> values) {
List<T> seletedItems = getValue();
wrapperPanel.clear();
checkBoxes.clear();
if (values.isEmpty()) {
throw new IllegalArgumentException("Widget has nothing to do");//$NON-NLS-1$
}
List<T> acceptableValues = (List<T>) values.toArray()[0];
for(T currentValue : acceptableValues) {
if(!checkBoxes.containsKey(currentValue)) {
addCheckBox(currentValue);
}
}
showCheckBoxes(seletedItems);
}
private void showCheckBoxes(List<T> seletedItems) {
for (Entry<T, CheckBox> currentEntry : checkBoxes.entrySet()) {
wrapperPanel.add(currentEntry.getValue());
if (seletedItems.contains(currentEntry.getKey())) {
currentEntry.getValue().setValue(true);
}
}
}
/**
* When the mapped ListModel does a setSelectedItem, this is invoked. This method sets checked, the checkboxes
* corresponding to the list passed to it.
* @param value
* list of checkboxes to set checked.
*/
@Override
public void setValue(List<T> value) {
setValue(value, false);
}
/**
* Calculate and obtain the list of checkboxes checked
* @return List of checkboxes checked
*/
@Override
public List<T> getValue() {
List<T> selectedItems = new ArrayList<>();
for (Entry<T, CheckBox> currentEntry : checkBoxes.entrySet()) {
if (currentEntry.getValue().getValue()) {
selectedItems.add(currentEntry.getKey());
}
}
return selectedItems;
}
}