view flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java @ 1436:29fc2d1dfe9b

Client side of interactive cross section diagrams. flys-client/trunk@3399 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Felix Wolfsteller <felix.wolfsteller@intevation.de>
date Tue, 13 Dec 2011 11:11:48 +0000
parents bc06a671ef60
children 432180235caf
line wrap: on
line source
package de.intevation.flys.client.client.ui.chart;

import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashMap;

import com.google.gwt.core.client.GWT;

import com.google.gwt.user.client.rpc.AsyncCallback;

import com.smartgwt.client.util.SC;

import com.smartgwt.client.types.Alignment; 
import com.smartgwt.client.types.ListGridFieldType;

import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.form.fields.SpinnerItem;
import com.smartgwt.client.widgets.form.DynamicForm;

import com.smartgwt.client.widgets.form.fields.ComboBoxItem;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;

import de.intevation.flys.client.client.Config;
import de.intevation.flys.client.shared.model.Artifact;
import de.intevation.flys.client.shared.model.Collection;
import de.intevation.flys.client.shared.model.CollectionItem;
import de.intevation.flys.client.shared.model.Data;
import de.intevation.flys.client.shared.model.DataItem;
import de.intevation.flys.client.shared.model.DefaultArtifact;
import de.intevation.flys.client.shared.model.DefaultData;
import de.intevation.flys.client.shared.model.DefaultDataItem;
import de.intevation.flys.client.shared.model.FacetRecord;
import de.intevation.flys.client.shared.model.OutputMode;
import de.intevation.flys.client.shared.model.Theme;
import de.intevation.flys.client.shared.model.ThemeList;

import de.intevation.flys.client.client.services.FeedServiceAsync;
import de.intevation.flys.client.client.services.CrossSectionKMServiceAsync;

/**
 * ThemePanel much like ChartThemePanel, but shows an "Actions" column,
 * needed for interaction in the CrossSection Charts and a selctor to
 * declare which cross section profile is "master".
 */
public class CrossSectionChartThemePanel
extends      ChartThemePanel {

    /** Service to query measurement points of cross sections. */
    CrossSectionKMServiceAsync kmService = GWT.create(
        de.intevation.flys.client.client.services.CrossSectionKMService.class);

    /** UUID of the current "master" cross section. */
    protected String currentMasterUUID;

    /** The layout (used for visual active/inactive feedback). */
    protected VLayout layout;

    /**
     * Trivial constructor.
     */
    public CrossSectionChartThemePanel(
        Collection collection,
        OutputMode mode)
    {
        super(collection, mode);
    }


    /**
     * Feed an artifact to let it know that it is master wrt cross-sections.
     * @param artifact uuid of an artifact.
     */
    public void feedTellMaster(final String artifact) {
        Data[] feedData = DefaultData.createSimpleStringDataArray(
            "cross_section.master?", "1");

        feedService.feed(
            Config.getInstance().getLocale(),
            new DefaultArtifact(artifact, "TODO:hash"),
            feedData,
            new AsyncCallback<Artifact>() {
                public void onFailure(Throwable caught) {
                    GWT.log("Could not feed artifact (" + artifact
                            + ") with master marker: " + caught.getMessage());
                    SC.warn(MSG.getString(caught.getMessage()));
                    enable();
                }
                public void onSuccess(Artifact artifact) {
                    GWT.log("Successfully injected master mark to " + artifact);
                    setCurrentMaster(artifact.getUuid());
                    requestRedraw();
                    enable();
                }
            });
    }


    /**
     * Create Layout, add a master selection box beneath.
     */
    @Override
    protected VLayout createLayout() {

        layout            = super.createLayout();

        // Create "set master" combobox.
        SelectItem cbItem = new SelectItem();

        cbItem.setTitle(MSG.chart_themepanel_set_master());
        cbItem.setType("comboBox");
        final LinkedHashMap<String, String> valueMap =
            getThemeList().toMapArtifactUUIDDescription("cross_section");
        cbItem.setValueMap(valueMap);

        final DynamicForm form = new DynamicForm();
        form.setWidth(200);
        form.setFields(cbItem);
        layout.addMember(form);

        Config config          = Config.getInstance();
        final String locale    = config.getLocale();

        // Add Change Handler to first unset the old master and then set the
        // new master.
        cbItem.addChangeHandler(new ChangeHandler() {
            public void onChange(ChangeEvent event) {  
                String selectedItem   = (String) event.getValue();
                final String artifact = selectedItem;

                disable();

                // Tell current master that he is not master anymore.
                if (getCurrentMaster() != null) {
                    Data[] feedData = DefaultData.createSimpleStringDataArray(
                            "cross_section.master?", "0");
                    feedService.feed(
                        locale,
                        new DefaultArtifact(getCurrentMaster(), "TODO:hash"),
                        feedData,
                        new AsyncCallback<Artifact>() {
                            public void onFailure(Throwable caught) {
                                GWT.log("Could not un-master artifact: " +
                                    caught.getMessage());
                                SC.warn(MSG.getString(caught.getMessage()));
                                enable();
                            }
                            public void onSuccess(Artifact oldMaster) {
                                GWT.log("Successfully un-mastered artifact.");
                                feedTellMaster(artifact);
                            }
                        });
                }
                else {
                    feedTellMaster(artifact);
                }
            }  
        });

        return layout;
    }


    /** Disable the UI (becomes gray, inresponsive to user input). */
    public void disable() {
        this.layout.setDisabled(true);
    }


    /** DisDisable the UI (becomes ungray, responsive to user input). */
    public void enable() {
        this.layout.setDisabled(false);
    }


    /**
     * Returns a double from the list that has the smallest distance to the
     * given to value. In case of multiple values with the same difference,
     * the last one is taken.
     * @param in possible return values.
     * @param to the value to be as close to as possible.
     * @return value from in that is closest to to, -1 if none.
     */
    public static double closest(Double[] in, double to) {
        if (in == null || in.length == 0) {
            return -1;
        }
        if (in[0] == to) {
            return to;
        }
        double minDiff = Math.abs(to - in[0]);
        double bestMatch = in[0];
        for (int i = 1; i < in.length; i++) {
            if (in[i] == to) {
                return to;
            }
            double diff = Math.abs(to - in[0]);
            if (diff < minDiff) {
                minDiff = diff;
                bestMatch = in[i];
            }
        }
        return bestMatch;
    }


    /** Feed a single artifact with the km of the crosssection to display. */
    public void sendFeed(FacetRecord facetRecord, double kmD) {
        Config config          = Config.getInstance();
        final String locale    = config.getLocale();

        Data[] feedData =
            DefaultData.createSimpleStringDataArray("cross_section.km",
                Double.valueOf(kmD).toString());

        disable();
        feedService.feed(
            locale,
            new DefaultArtifact(
                facetRecord.getTheme().getArtifact(),
                "TODO:hash"),
            feedData,
            new AsyncCallback<Artifact>() {
                @Override
                public void onFailure(Throwable caught) {
                    GWT.log("Could not feed artifact " + caught.getMessage());
                    SC.warn(MSG.getString(caught.getMessage()));
                    enable();
                }
                @Override
                public void onSuccess(Artifact artifact) {
                    GWT.log("Successfully fed");
                    requestRedraw();
                    enable();
                }
        });
    }


    /**
     * Create the handler for ChangeEvents on the Spinner in the
     * facets that control km of cross section.
     *
     * @param feedService The FeedService to send (changed) data to.
     * @param facetRecord The FacetRecord (~row in table) where this
     *                    handler is added to (to a child, to be exact).
     */
    public final ChangedHandler createSpinnerHandler(
        final FeedServiceAsync feedService,
        final FacetRecord      facetRecord)
    {
        Config config          = Config.getInstance();
        final String locale    = config.getLocale();

        ChangedHandler handler = new ChangedHandler()
            {
                @Override
                public void onChanged(final ChangedEvent ce) {
                    if (ce.getValue() == null) {
                        return;
                    }

                    // Now, query the value with the kmService.
                    Map<Integer, Double> map = new HashMap<Integer,Double>();
                    final double selected_km =
                        Double.parseDouble(ce.getValue().toString());
                    
                    int _dbid = -1;
                    try {
                        _dbid = Integer.valueOf(facetRecord.getTheme()
                            .getCollectionItem()
                            .getData().get("cross_section.dbid"));
                    }
                    catch (NumberFormatException nfe) {
                        GWT.log("Could not extract cross-section db id from data.");
                    }
                    final int dbid = _dbid;

                    map.put(dbid, Double.parseDouble(ce.getValue().toString()));

                    disable();
    
                    kmService.getCrossSectionKMs(locale, map, 2,
                        new AsyncCallback<Map<Integer, Double[]>>() {
                            @Override
                            public void onFailure(Throwable caught) {
                                GWT.log("Could not get single km for "
                                    + dbid + ": "+ caught.getMessage());
                                SC.warn(MSG.getString(caught.getMessage()));
                                updateCollection();
                                updateGrid();
                                enable();
                            }
                            @Override
                            public void onSuccess(Map<Integer, Double[]> obj) {
                                Double[] kms = obj.get(dbid);
                                double closest =
                                    CrossSectionChartThemePanel.closest(kms,
                                        selected_km);
                                GWT.log("Got single km for " + dbid + ", it is "
                                    + closest);
                                SpinnerItem item = (SpinnerItem) ce.getItem();
                                item.setValue(closest);
                                sendFeed(facetRecord, closest);
                            }
                            });
                }
            };
        return handler;
    }


    /**
     * Get the chosen km for given cross-section.
     */
    final public double getKmForCrossSectionIdx(final int index) {
        double km   = 0d;
        int collIdx = 0;
        while (km == 0d) {
            CollectionItem item = collection.getItem(collIdx++);
            if (item == null) {
                break;
            }
            if (item.getData() == null) continue;
            String d = item.getData().get("cross_section" + index + ".km");
            if (d == null) {
                continue;
            }
            km = Double.valueOf(d);
        }
        return km;
    }


    /**
     * Create and configure the Grid to display.
     */
    @Override
    protected ListGrid createGrid() {
        ListGrid list = new ListGrid() {
            @Override
            protected Canvas createRecordComponent(
                final ListGridRecord record,
                Integer colNum)
            {
                    // Only cross_section Facets display an action widget.
                    final FacetRecord facetRecord = (FacetRecord) record;
                    if (!facetRecord.getTheme().getFacet().equals(
                        "cross_section"))
                    {
                        return null;
                    }

                    String fieldName = this.getFieldName(colNum);

                    if (fieldName.equals(GRID_FIELD_ACTIONS)) {
                        HLayout recordCanvas = new HLayout(3);
                        recordCanvas.setHeight(22);
                        recordCanvas.setAlign(Alignment.CENTER);
                        // TODO Refactor in createSpinner(data, artifact)
                        SpinnerItem spinnerItem = new SpinnerItem();
                        spinnerItem.setShowTitle(false);
                        spinnerItem.setTitle("Waterlevel-Spinner");
                        spinnerItem.setWidth(45);
                        // TODO actually get the value from artifact
                        spinnerItem.setDefaultValue(
                            getKmForCrossSectionIdx(facetRecord.getTheme().getIndex()));

                        // TODO actually use crosssectionkmservive
                        spinnerItem.setMin(0);
                        spinnerItem.setMax(1000);
                        spinnerItem.setStep(5f);
                        spinnerItem.setChangeOnKeypress(true);

                        spinnerItem.addChangedHandler(
                            createSpinnerHandler(
                                feedService,
                                facetRecord));

                        DynamicForm formWrap = new DynamicForm();
                        formWrap.setFields(spinnerItem);
                        formWrap.setTitlePrefix("");
                        formWrap.setTitleSuffix("");
                        recordCanvas.addMember(formWrap);
                        return recordCanvas;
                    }
                    else {
                        return null;
                    }
                }
            };
        list.setCanResizeFields(true);
        list.setShowRecordComponents(true);
        list.setShowRecordComponentsByCell(true);
        list.setShowAllRecords(true);
        list.setShowHeaderContextMenu(false);
        return list;
    }


    /**
     * Initializes the components (columns) of the theme grid.
     */
    @Override
    protected void initGrid() {
        list.setCanEdit(true);
        list.setCanSort(false);
        list.setShowRecordComponents(true);
        list.setShowRecordComponentsByCell(true);
        list.setShowHeader(true);
        list.setWidth100();
        list.setHeight100();

        list.addEditCompleteHandler(this);

        ListGridField active = new ListGridField(GRID_FIELD_ACTIVE, " ", 20);
        active.setType(ListGridFieldType.BOOLEAN);

        ListGridField name = new ListGridField(
            GRID_FIELD_NAME, MSG.chart_themepanel_header_themes());
        name.setType(ListGridFieldType.TEXT);

        ListGridField actions = new ListGridField(GRID_FIELD_ACTIONS,
             MSG.chart_themepanel_header_actions(), 50);

        list.setFields(active, name, actions);
    }


    /** Get Current Cross-section Masters uuid. */
    public String getCurrentMaster() {
        return currentMasterUUID;
    }


    /** Set Current Cross-section Masters uuid. */
    public void setCurrentMaster(String currentMasterUuid) {
        this.currentMasterUUID = currentMasterUuid;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org