package org.activityinfo.ui.client.component.filter;
/*
* #%L
* ActivityInfo Server
* %%
* Copyright (C) 2009 - 2013 UNICEF
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import com.extjs.gxt.ui.client.Style;
import com.extjs.gxt.ui.client.data.*;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.LoadListener;
import com.extjs.gxt.ui.client.event.TreePanelEvent;
import com.extjs.gxt.ui.client.store.Store;
import com.extjs.gxt.ui.client.store.StoreEvent;
import com.extjs.gxt.ui.client.store.StoreListener;
import com.extjs.gxt.ui.client.store.TreeStore;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.form.StoreFilterField;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.toolbar.LabelToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
import com.extjs.gxt.ui.client.widget.treepanel.TreePanel;
import com.extjs.gxt.ui.client.widget.treepanel.TreePanel.CheckCascade;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.rpc.AsyncCallback;
import org.activityinfo.i18n.shared.I18N;
import org.activityinfo.legacy.client.Dispatcher;
import org.activityinfo.legacy.client.callback.SuccessCallback;
import org.activityinfo.legacy.client.monitor.MaskingAsyncMonitor;
import org.activityinfo.legacy.shared.Log;
import org.activityinfo.legacy.shared.command.GetActivityForm;
import org.activityinfo.legacy.shared.command.GetSchema;
import org.activityinfo.legacy.shared.model.*;
import org.activityinfo.promise.Promise;
import org.activityinfo.ui.client.style.legacy.icon.IconImageBundle;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* UI Component that allows the user to select a list of Indicators
*
* @author Alex Bertram
*/
public class IndicatorTreePanel extends ContentPanel {
private final Dispatcher dispatcher;
private final TreeStore<ModelData> store;
private final TreePanel<ModelData> tree;
private ToolBar toolBar;
private StoreFilterField filter;
private boolean multipleSelection;
/**
* Keep our own copy of our selection state that is independent of the
* loading process
*/
private Set<Integer> selection = Sets.newHashSet();
public IndicatorTreePanel(Dispatcher dispatcher, final boolean multipleSelection) {
this.dispatcher = dispatcher;
this.setHeadingText(I18N.CONSTANTS.indicators());
this.setIcon(IconImageBundle.ICONS.indicator());
this.setLayout(new FitLayout());
this.setScrollMode(Style.Scroll.NONE);
store = new TreeStore<ModelData>(new Loader());
setStoreKeyProvider();
tree = new TreePanel<ModelData>(store);
tree.setCheckNodes(TreePanel.CheckNodes.BOTH);
tree.setCheckStyle(CheckCascade.CHILDREN);
tree.getStyle().setNodeCloseIcon(null);
tree.getStyle().setNodeOpenIcon(null);
tree.setLabelProvider(new NodeLabelProvider());
tree.setCheckable(true);
tree.expandAll();
tree.setStateId("indicatorPanel");
tree.setStateful(true);
tree.setAutoSelect(true);
tree.addListener(Events.BrowserEvent, new Listener<TreePanelEvent<ModelData>>() {
@Override
public void handleEvent(TreePanelEvent<ModelData> be) {
if (be.getEventTypeInt() == Event.ONKEYPRESS) {
if (!toolBar.isVisible()) {
toolBar.setVisible(true);
}
filter.focus();
}
}
});
add(tree);
createFilterBar();
tree.getStore().getLoader().addLoadListener(new LoadListener() {
@Override
public void loaderLoad(LoadEvent le) {
Log.info("Loaded " + le);
}
});
tree.getStore().addStoreListener(new StoreListener<ModelData>() {
@Override
public void storeDataChanged(StoreEvent<ModelData> se) {
// apply our internal state to the newly loaded list
applySelection();
}
});
addCheckChangedListener(new Listener<TreePanelEvent>() {
@Override
public void handleEvent(TreePanelEvent be) {
if (be.getItem() instanceof IndicatorDTO) {
IndicatorDTO indicator = (IndicatorDTO) be.getItem();
if (be.isChecked()) {
selection.add(indicator.getId());
} else {
selection.remove(indicator.getId());
}
} else if (be.isChecked()) {
tree.getStore().getLoader().loadChildren(be.getItem());
}
}
});
}
private void setStoreKeyProvider() {
store.setKeyProvider(new ModelKeyProvider<ModelData>() {
@Override
public String getKey(ModelData model) {
new ArrayList<String>();
if (model instanceof ProvidesKey) {
return ((ProvidesKey) model).getKey();
} else if (model == null) {
throw new IllegalStateException(
"Did not expect model to be null: assigning keys in IndicatorTreePanel");
}
throw new IllegalStateException("Unknown type: expected activity, userdb, indicator or indicatorgroup");
}
});
}
@Override
public void setHeadingText(String heading) {
super.setHeadingText(heading);
}
private void createFilterBar() {
toolBar = new ToolBar();
toolBar.add(new LabelToolItem(I18N.CONSTANTS.search()));
filter = new FilterField();
filter.addListener(Events.Blur, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
if (filter.getRawValue() == null || filter.getRawValue().length() == 0) {
toolBar.setVisible(false);
}
}
});
toolBar.add(filter);
toolBar.setVisible(false);
filter.bind(store);
setTopComponent(toolBar);
}
private final class NodeLabelProvider implements ModelStringProvider<ModelData> {
@Override
public String getStringValue(ModelData model, String property) {
String name = model.get("name");
if (model instanceof IndicatorDTO) {
return name;
} else {
if (name == null) {
name = "noname";
}
return "<b>" + name + "</b>";
}
}
}
private class Proxy implements DataProxy<List<ModelData>> {
private SchemaDTO schema;
@Override
public void load(DataReader<List<ModelData>> listDataReader,
Object parent,
final AsyncCallback<List<ModelData>> callback) {
if (parent == null) {
dispatcher.execute(new GetSchema(),
new MaskingAsyncMonitor(IndicatorTreePanel.this, I18N.CONSTANTS.loading()),
new SuccessCallback<SchemaDTO>() {
@Override
public void onSuccess(SchemaDTO result) {
schema = result;
callback.onSuccess(new ArrayList<ModelData>(schema.getDatabases()));
}
});
} else if (parent instanceof UserDatabaseDTO) {
callback.onSuccess(new ArrayList<ModelData>(createDatabaseChildren((UserDatabaseDTO) parent)));
} else if (parent instanceof ActivityCategory) {
callback.onSuccess(new ArrayList<ModelData>(((ActivityCategory) parent).getActivities()));
} else if (parent instanceof ActivityDTO) {
loadActivityChildren((ActivityDTO) parent).then(callback);
} else if (parent instanceof IndicatorGroup) {
callback.onSuccess(createIndicatorList((IndicatorGroup) parent));
}
}
}
private List<ModelData> createDatabaseChildren(UserDatabaseDTO databaseDTO) {
List<ModelData> children = new ArrayList<ModelData>();
Set<ActivityCategory> categories = new HashSet<ActivityCategory>();
for (ActivityDTO activityDTO : databaseDTO.getActivities()) {
if (activityDTO.hasCategory()) {
ActivityCategory activityCategory = new ActivityCategory(databaseDTO.getId(), activityDTO.getCategory());
categories.add(activityCategory);
if (!children.contains(activityCategory)) {
children.add(activityCategory);
}
} else {
children.add(activityDTO);
}
}
// fill category with activities
for (ActivityCategory category : categories) {
for (ActivityDTO activityDTO : databaseDTO.getActivities()) {
if (category.getName().equals(activityDTO.getCategory())) {
category.addActivity(activityDTO);
}
}
}
return children;
}
private Promise<List<ModelData>> loadActivityChildren(ActivityDTO activity) {
return dispatcher.execute(new GetActivityForm(activity.getId())).then(new Function<ActivityFormDTO, List<ModelData>>() {
@Nullable
@Override
public List<ModelData> apply(@Nullable ActivityFormDTO form) {
List<IndicatorGroup> groupIndicators = form.groupIndicators();
List<ModelData> children = new ArrayList<ModelData>();
for (IndicatorGroup group : groupIndicators) {
if (group.getName() == null) {
for (IndicatorDTO indicator : group.getIndicators()) {
children.add(indicator);
}
} else {
children.add(group);
}
}
return children;
}
});
}
private List<ModelData> createIndicatorList(IndicatorGroup group) {
ArrayList<ModelData> list = new ArrayList<ModelData>();
for (IndicatorDTO indicator : group.getIndicators()) {
list.add(indicator);
}
return list;
}
/**
* @return the list of selected indicators
*/
public List<IndicatorDTO> getSelection() {
List<IndicatorDTO> list = new ArrayList<IndicatorDTO>();
for (ModelData model : tree.getCheckedSelection()) {
if (model instanceof IndicatorDTO) {
list.add((IndicatorDTO) model);
}
}
return list;
}
public void select(int indicatorId, boolean select) {
if (select) {
if (!multipleSelection) {
selection.clear();
}
selection.add(indicatorId);
} else {
selection.remove(indicatorId);
}
// apply directly if the indicators are loaded
for (ModelData model : tree.getStore().getAllItems()) {
if (model instanceof IndicatorDTO && ((IndicatorDTO) model).getId() == indicatorId) {
setChecked((IndicatorDTO) model, select);
}
}
}
public void setSelection(Iterable<Integer> indicatorIds) {
selection = Sets.newHashSet(indicatorIds);
applySelection();
}
public void addCheckChangedListener(Listener<TreePanelEvent> listener) {
tree.addListener(Events.CheckChange, listener);
}
public void addBeforeCheckedListener(Listener<TreePanelEvent> listener) {
tree.addListener(Events.BeforeCheckChange, listener);
}
/**
* @return the list of the ids of selected indicators
*/
public List<Integer> getSelectedIds() {
return Lists.newArrayList(selection);
}
public void setMultipleSelection(boolean multipleSelection) {
this.multipleSelection = multipleSelection;
if (multipleSelection) {
tree.setCheckStyle(TreePanel.CheckCascade.CHILDREN);
} else {
tree.setCheckStyle(TreePanel.CheckCascade.NONE);
}
}
private class Loader extends BaseTreeLoader<ModelData> {
public Loader() {
super(new Proxy());
}
@Override
public boolean hasChildren(ModelData parent) {
return !(parent instanceof IndicatorDTO);
}
}
private static class FilterField extends StoreFilterField {
@Override
protected boolean doSelect(Store store, ModelData parent, ModelData record, String property, String filter) {
String[] keywords = filter.toLowerCase().split("\\s+");
String name = ((String) record.get("name")).toLowerCase();
for (String keyword : keywords) {
if (name.indexOf(keyword) == -1) {
return false;
}
}
return true;
}
}
public boolean isMultipleSelection() {
return multipleSelection;
}
public void clearSelection() {
for (IndicatorDTO indicator : getSelection()) {
tree.getSelectionModel().deselect(indicator);
tree.setChecked(indicator, false);
}
}
public void setChecked(IndicatorDTO indicator, boolean b) {
tree.setChecked(indicator, b);
}
public void setLeafCheckableOnly() {
tree.setCheckNodes(TreePanel.CheckNodes.LEAF);
}
private void applySelection() {
for (ModelData model : tree.getStore().getAllItems()) {
if (model instanceof IndicatorDTO) {
IndicatorDTO indicator = (IndicatorDTO) model;
boolean selected = selection.contains(indicator.getId());
setChecked(indicator, selected);
}
}
}
}