package org.activityinfo.ui.client.component.report.editor.map;
/*
* #%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.dnd.DND.Feedback;
import com.extjs.gxt.ui.client.dnd.ListViewDragSource;
import com.extjs.gxt.ui.client.dnd.ListViewDropTarget;
import com.extjs.gxt.ui.client.event.*;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.ListView;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.layout.AnchorData;
import com.extjs.gxt.ui.client.widget.layout.AnchorLayout;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.extjs.gxt.ui.client.widget.menu.MenuItem;
import com.extjs.gxt.ui.client.widget.menu.SeparatorMenuItem;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.inject.Inject;
import org.activityinfo.i18n.shared.I18N;
import org.activityinfo.legacy.client.Dispatcher;
import org.activityinfo.legacy.shared.reports.model.MapReportElement;
import org.activityinfo.legacy.shared.reports.model.clustering.NoClustering;
import org.activityinfo.legacy.shared.reports.model.layers.MapLayer;
import org.activityinfo.legacy.shared.reports.model.layers.PointMapLayer;
import org.activityinfo.ui.client.EventBus;
import org.activityinfo.ui.client.component.report.editor.map.layerOptions.LayerOptionsPanel;
import org.activityinfo.ui.client.page.report.HasReportElement;
import org.activityinfo.ui.client.page.report.ReportChangeHandler;
import org.activityinfo.ui.client.page.report.ReportEventBus;
import org.activityinfo.ui.client.style.legacy.icon.IconImageBundle;
import org.activityinfo.ui.client.widget.wizard.WizardCallback;
import org.activityinfo.ui.client.widget.wizard.WizardDialog;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Displays a list of layers selected by the user
*/
public final class LayersWidget extends LayoutContainer implements HasReportElement<MapReportElement> {
public static final int WIDTH = 225;
private static final int CONTEXT_MENU_WIDTH = 150;
private final ReportEventBus reportEventBus;
private Dispatcher service;
private MapReportElement mapElement;
private ListStore<LayerModel> store = new ListStore<LayerModel>();
private ListView<LayerModel> view = new ListView<LayerModel>();
private ContentPanel layersPanel;
private WizardDialog addLayersDialog;
private LayerOptionsPanel optionsPanel;
private BaseMapPanel baseMapPanel;
private Menu layerMenu;
private MenuItem clusterMenuItem;
@Inject
public LayersWidget(Dispatcher service, EventBus eventBus, LayerOptionsPanel optionsPanel) {
super();
this.service = service;
this.reportEventBus = new ReportEventBus(eventBus, this);
this.reportEventBus.listen(new ReportChangeHandler() {
@Override
public void onChanged() {
updateStore();
}
});
this.optionsPanel = optionsPanel;
createDefaultMapReportElement();
initializeComponent();
createLayersPanel();
createAddLayerButton();
createListView();
createBaseMapPanel();
}
private void createDefaultMapReportElement() {
mapElement = new MapReportElement();
}
private void createAddLayerButton() {
Button addLayerButton = new Button();
addLayerButton.setText(I18N.CONSTANTS.add());
addLayerButton.addListener(Events.Select, new SelectionListener<ButtonEvent>() {
@Override
public void componentSelected(ButtonEvent ce) {
final NewLayerWizard wizard = new NewLayerWizard(service);
addLayersDialog = new WizardDialog(wizard);
addLayersDialog.show(new WizardCallback() {
@Override
public void onFinished() {
addLayer(wizard.createLayer());
}
});
}
});
addLayerButton.setIcon(IconImageBundle.ICONS.add());
layersPanel.getHeader().addTool(addLayerButton);
}
private void initializeComponent() {
AnchorLayout anchorLayout = new AnchorLayout();
setLayout(anchorLayout);
setWidth(WIDTH);
}
private void createLayersPanel() {
layersPanel = new ContentPanel();
layersPanel.setCollapsible(false);
layersPanel.setFrame(true);
layersPanel.setHeadingText(I18N.CONSTANTS.layers());
layersPanel.setBodyBorder(false);
layersPanel.setHeaderVisible(true);
layersPanel.setIcon(AbstractImagePrototype.create(MapResources.INSTANCE.layers()));
AnchorData layoutData = new AnchorData();
layoutData.setAnchorSpec("100% none");
add(layersPanel, layoutData);
}
private void createBaseMapPanel() {
baseMapPanel = new BaseMapPanel(service);
baseMapPanel.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
mapElement.setBaseMapId(event.getValue());
reportEventBus.fireChange();
}
});
AnchorData layoutData = new AnchorData();
layoutData.setAnchorSpec("100% none");
add(baseMapPanel, layoutData);
}
private void createListView() {
view.setStore(store);
view.setTemplate(MapResources.INSTANCE.layerTemplate().getText());
view.setItemSelector(".layerItem");
// Prevents confusion for the user where an onmouseover-ed item in the
// listview *looks* selected,
// but in fact is not selected
// Off for now. It's a choice between two evils: confusing layer removal
// and
// confusing layer selection
// view.setSelectOnOver(true);
addListViewDnd();
view.addListener(Events.Select, new Listener<ListViewEvent<LayerModel>>() {
@Override
public void handleEvent(ListViewEvent<LayerModel> event) {
onLayerSelected(event);
}
});
layersPanel.add(view);
}
private void onLayerSelected(ListViewEvent<LayerModel> event) {
if (event.getIndex() == -1) {
optionsPanel.hide();
}
// Change visibility
if (event.getTargetEl().hasStyleName("x-view-item-checkbox")) {
LayerModel layerModel = event.getModel();
if (layerModel != null) {
boolean newSetting = !layerModel.isVisible();
layerModel.setVisible(newSetting);
layerModel.getMapLayer().setVisible(newSetting);
reportEventBus.fireChange();
store.update(layerModel);
}
} else {
showOptionsMenu(event.getModel().getMapLayer(), event.getIndex());
}
optionsPanel.onLayerSelectionChanged(event.getModel().getMapLayer());
}
public void shutdown() {
view.removeAllListeners();
if (layerMenu != null) {
layerMenu.hide();
layerMenu.removeAllListeners();
}
if (addLayersDialog != null) {
addLayersDialog.hide();
addLayersDialog.removeAllListeners();
}
}
private MapLayer getSelectedLayer() {
return view.getSelectionModel().getSelectedItem().getMapLayer();
}
private void showOptionsMenu(MapLayer mapLayer, int index) {
if (layerMenu == null) {
createLayerMenu();
}
int x = this.getAbsoluteLeft() - CONTEXT_MENU_WIDTH;
int y = view.getElement(index).getAbsoluteTop();
clusterMenuItem.setVisible(mapLayer instanceof PointMapLayer);
layerMenu.showAt(x, y);
}
private void createLayerMenu() {
layerMenu = new Menu();
layerMenu.add(new MenuItem(I18N.CONSTANTS.style(),
AbstractImagePrototype.create(MapResources.INSTANCE.styleIcon()),
new SelectionListener<MenuEvent>() {
@Override
public void componentSelected(MenuEvent ce) {
optionsPanel.showStyle(getSelectedLayer());
}
}));
clusterMenuItem = new MenuItem(I18N.CONSTANTS.clustering(),
AbstractImagePrototype.create(MapResources.INSTANCE.clusterIcon()),
new SelectionListener<MenuEvent>() {
@Override
public void componentSelected(MenuEvent ce) {
optionsPanel.showAggregation(getSelectedLayer());
}
});
layerMenu.add(clusterMenuItem);
layerMenu.add(new MenuItem(I18N.CONSTANTS.filter(),
IconImageBundle.ICONS.filter(),
new SelectionListener<MenuEvent>() {
@Override
public void componentSelected(MenuEvent ce) {
optionsPanel.showFilter(getSelectedLayer());
}
}));
layerMenu.add(new SeparatorMenuItem());
layerMenu.add(new MenuItem(I18N.CONSTANTS.delete(),
IconImageBundle.ICONS.delete(),
new SelectionListener<MenuEvent>() {
@Override
public void componentSelected(MenuEvent ce) {
removeLayer(getSelectedLayer());
}
}));
layerMenu.setWidth(CONTEXT_MENU_WIDTH);
}
private void addListViewDnd() {
ListViewDropTarget target = new MapLayersDropTarget(view);
target.setAllowSelfAsSource(true);
target.setFeedback(Feedback.INSERT);
new LayerListViewDragSource(view);
}
private void removeLayer(MapLayer mapLayer) {
mapElement.getLayers().remove(mapLayer);
reportEventBus.fireChange();
updateStore();
if (optionsPanel.getValue() == mapLayer) {
optionsPanel.fadeOut();
}
}
@Override
public void bind(MapReportElement model) {
this.mapElement = model;
this.baseMapPanel.setValue(model.getBaseMapId());
updateStore();
}
@Override
public MapReportElement getModel() {
return mapElement;
}
private void updateStore() {
// Save the selecteditem, because removing all items from the store
// triggers
// a selecteditem change
int selectedItemIndex = store.indexOf(view.getSelectionModel().getSelectedItem());
store.removeAll();
if (mapElement != null) {
for (MapLayer layer : mapElement.getLayers()) {
LayerModel model = new LayerModel();
model.setName(layer.getName());
model.setVisible(layer.isVisible());
model.setMapLayer(layer);
model.setLayerType(layer.getTypeName());
store.add(model);
}
}
// Place selection back at original selection
if ((selectedItemIndex != -1) && (selectedItemIndex < store.getCount())) {
List<LayerModel> selectedItem = new ArrayList<LayerModel>();
selectedItem.add(store.getAt(selectedItemIndex));
view.getSelectionModel().setSelection(selectedItem);
}
}
public void addLayer(MapLayer layer) {
if (layer instanceof PointMapLayer) {
((PointMapLayer) layer).setClustering(new NoClustering());
}
mapElement.getLayers().add(layer);
reportEventBus.fireChange();
updateStore();
}
private final class LayerListViewDragSource extends ListViewDragSource {
private int draggedItemIndexStart = 0;
private int draggedItemIndexDrop = 0;
private LayerListViewDragSource(ListView listView) {
super(listView);
}
@Override
protected void onDragStart(DNDEvent e) {
super.onDragStart(e);
if (!e.getTargetEl().hasStyleName("grabSprite")) {
e.setCancelled(true);
}
draggedItemIndexStart = store.indexOf(view.getSelectionModel().getSelectedItem());
e.setData(draggedItemIndexStart);
}
@Override
protected void onDragDrop(DNDEvent e) {
super.onDragDrop(e);
// Move the MapLayer onto his new position
draggedItemIndexDrop = ((MapLayersDropTarget) e.getDropTarget()).getInsertIndex();
if (draggedItemIndexDrop == mapElement.getLayers().size()) {
draggedItemIndexDrop--;
}
Collections.swap(mapElement.getLayers(), draggedItemIndexStart, draggedItemIndexDrop);
reportEventBus.fireChange();
}
}
private class MapLayersDropTarget extends ListViewDropTarget {
public MapLayersDropTarget(ListView listView) {
super(listView);
}
public int getInsertIndex() {
return insertIndex;
}
}
@Override
public void disconnect() {
reportEventBus.disconnect();
}
}