view flys-client/src/main/java/de/intevation/flys/client/client/ui/DatacageTwinPanel.java @ 1333:1627a28c4504

Cosmetics, docs. flys-client/trunk@2978 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Felix Wolfsteller <felix.wolfsteller@intevation.de>
date Mon, 17 Oct 2011 09:41:40 +0000
parents cfbfaadf4b6f
children 46a4b74d87bf
line wrap: on
line source
package de.intevation.flys.client.client.ui;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;
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.events.ClickEvent;
import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
import com.smartgwt.client.widgets.grid.events.RecordClickHandler;

import de.intevation.flys.client.shared.model.Artifact;
import de.intevation.flys.client.shared.model.Collection;
import de.intevation.flys.client.shared.model.Data;
import de.intevation.flys.client.shared.model.DataItem;
import de.intevation.flys.client.shared.model.DefaultData;
import de.intevation.flys.client.shared.model.DefaultDataItem;
import de.intevation.flys.client.shared.model.DataList;
import de.intevation.flys.client.shared.model.User;

import de.intevation.flys.client.client.FLYSConstants;
import de.intevation.flys.client.client.event.StepForwardEvent;
import de.intevation.flys.client.shared.model.Recommendation;
import de.intevation.flys.client.shared.model.Recommendation.Facet;
import de.intevation.flys.client.shared.model.Recommendation.Filter;

import de.intevation.flys.client.client.Config;
import de.intevation.flys.client.client.services.LoadArtifactService;
import de.intevation.flys.client.client.services.LoadArtifactServiceAsync;
import de.intevation.flys.client.client.services.RemoveArtifactService;
import de.intevation.flys.client.client.services.RemoveArtifactServiceAsync;

// TODO Probably better to branch off AbstractUIProvider.
// TODO Merge with other datacage-widget impls.
/**
 * Panel containing a Grid and a "next" button. The Grid is fed by a
 * DatacagePairWidget which is put in the input-helper area.
 */
public class DatacageTwinPanel
extends      TextProvider {

    protected static FLYSConstants MSG = GWT.create(FLYSConstants.class);

    protected String dataName;

    protected User user;

    /** ListGrid that displays user-selected pairs to build differences with. */
    protected ListGrid differencesList;

    /*
     * List to track previously selected but now removed pairs. (Needed to
     * be able to identify artifacts that can be removed from the collection.
     */
    protected List<RecommendationPairRecord> removedPairs =
        new ArrayList<RecommendationPairRecord>();

    /** Service handle to clone and add artifacts to collection. */
    LoadArtifactServiceAsync loadArtifactService = GWT.create(
            de.intevation.flys.client.client.services.LoadArtifactService.class);

    /** Service to remove artifacts from collection. */
    RemoveArtifactServiceAsync removeArtifactService = GWT.create(
            de.intevation.flys.client.client.services.RemoveArtifactService.class);

    public DatacageTwinPanel(User user) {
        super();
        this.user = user;
    }


    /**
     * Remove first occurrence of "[" and "]" (if both do occur).
     * @param value String to be stripped of [] (might be null).
     * @return input string but with [ and ] removed, or input string if no
     *         brackets were found.
     * @see StringUtil.unbracket
     */
    public static final String unbracket(String value) {
        // null- guard.
        if (value == null) return value;

        int start = value.indexOf("[");
        int end   = value.indexOf("]");

        if (start < 0 || end < 0) {
            return value;
        }

        value = value.substring(start + 1, end);

        return value;
    }


    /**
     * Create a recommendation from a string representation of it.
     * @TODO describe format of input string
     * @param from string in format as shown above.
     * @return recommendation from input string.
     */
    public Recommendation createRecommendationFromString(String from) {
        // TODO Construct "real" filter.
        String[] parts = unbracket(from).split(";");
        Recommendation.Filter filter = new Recommendation.Filter();
        Recommendation.Facet  facet  = new Recommendation.Facet(
                parts[1],
                parts[2]);

        List<Recommendation.Facet> facets = new ArrayList<Recommendation.Facet>
            ();
        facets.add(facet);
        filter.add("longitudinal_section", facets);
        Recommendation r = new Recommendation("waterlevel", parts[0],
            this.artifact.getUuid(), filter);
        return r; 
    }


    /**
     * Add RecomendationPairRecords from input String to the ListGrid.
     */
    public void populateGridFromString(String from){
        // Split this string.
        // Create according recommendations and display strings.
        String[] recs = from.split("#");
        // TODO real name
        if (recs.length % 2 != 0) return;
        for (int i = 0; i < recs.length; i+=2) {
            Recommendation minuend =
                createRecommendationFromString(recs[i+0]);
            Recommendation subtrahend =
                createRecommendationFromString(recs[i+1]);
            RecommendationPairRecord pr = new RecommendationPairRecord(
               "A", minuend, "B", subtrahend);
            // This Recommendation Pair comes from the data string and was thus
            // already cloned.
            pr.setIsAlreadyLoaded(true);
            this.differencesList.addData(pr);
        }
    }


    /**
     * Creates the graphical representation and interaction widgets for the data.
     * @param dataList the data.
     * @return graphical representation and interaction widgets for data.
     */
    @Override
    public Canvas create(DataList dataList) {
        GWT.log("createData()");

        Canvas widget = createWidget();
        Canvas submit = getNextButton();

        VLayout layout       = new VLayout();
        HLayout helperLayout = new HLayout();
        helperLayout.addMember(new DatacagePairWidget(this.artifact,
            user, "waterlevels", differencesList));

        layout.addMember(widget);
        layout.addMember(submit);
        layout.setMembersMargin(10);
        this.helperContainer.addMember(helperLayout);

        // Find old data, if any, handle "diffids".
        Data   data   = dataList.get(0);
        this.dataName = data.getLabel();
        for (int i = 0; i < dataList.size(); i++) {
            if (dataList.get(i) != null && dataList.get(i).getItems() != null) {
                if (dataList.get(i).getItems() != null) {
                    populateGridFromString(
                        dataList.get(i).getItems()[0].getStringValue());
                }
            }
        }

        return layout;
    }


    /**
     * Validates the selection.
     * @return List of internationalized errror messages (if any).
     */
    @Override
    public List<String> validate() {
        List<String> errors = new ArrayList<String>();
        if (differencesList.getRecords().length == 0) {
            errors.add(MSG.error_no_waterlevel_pair_selected());
        }
        // Check whether minuend and subtrahend are equal.
        for (ListGridRecord record: differencesList.getRecords()) {
            RecommendationPairRecord r = (RecommendationPairRecord) record;
            if (r.getFirst().equals(r.getSecond())) {
                errors.add(MSG.error_same_waterlevels_in_pair());
            }
        }

        return errors;
    }


    /**
     * Creates layout with grid that displays selection inside.
     */
    public Canvas createWidget() {
        VLayout layout  = new VLayout();
        differencesList = new ListGrid();

        differencesList.setCanEdit(false);
        differencesList.setCanSort(false);
        differencesList.setHeight(150);
        differencesList.setShowAllRecords(true);

        ListGridField nameField    = new ListGridField("first",  "Minuend");
        ListGridField capitalField = new ListGridField("second", "Subtrahend");
        // Track removed rows, therefore more or less reimplement
        // setCanRecomeRecords.
        final ListGridField removeField  =
            new ListGridField("_removeRecord", "Remove Record"){{
                setType(ListGridFieldType.ICON);
                setIcon("[SKIN]/actions/remove.png");
                setCanEdit(false);
                setCanFilter(false);
                setCanSort(false);
                setCanGroupBy(false);
                setCanFreeze(false);
                setWidth(25);
        }};

        differencesList.setFields(new ListGridField[] {nameField,
           capitalField, removeField});
        
        differencesList.addRecordClickHandler(new RecordClickHandler() {
                public void onRecordClick(final RecordClickEvent event) {
                    // Just handle remove-clicks
                    if(!event.getField().getName().equals(removeField.getName())) {
                        return;
                    }    
                    trackRemoved(event.getRecord());
                    event.getViewer().removeData(event.getRecord());
                }
            });
        layout.addMember(differencesList);

        return layout;
    }


    /**
     * Add record to list of removed records.
     */
    public void trackRemoved(Record r) {
        RecommendationPairRecord pr = (RecommendationPairRecord) r;
        this.removedPairs.add(pr);
    }


    /**
     * Validates data, does nothing if invalid, otherwise clones new selected
     * waterlevels and add them to collection, forward the artifact.
     */
    @Override
    public void onClick(ClickEvent e) {
        GWT.log("DatacageTwinPanel.onClick");

        List<String> errors = validate();
        if (errors != null && !errors.isEmpty()) {
            showErrors(errors);
            return;
        }

        Config config = Config.getInstance();
        ListGridRecord[] records = differencesList.getRecords();

        List<Recommendation> ar  = new ArrayList<Recommendation>();
        List<Recommendation> all = new ArrayList<Recommendation>();
        List<Recommendation> statics = new ArrayList<Recommendation>();
        for (ListGridRecord record : records) {
            RecommendationPairRecord r =
                (RecommendationPairRecord) record;
            // Do not add "old" recommendations.
            if (!r.isAlreadyLoaded()) {
                // Check whether one of those is a dike or similar.
                // TODO differentiate and merge: new clones, new, old.
                if(r.getFirst().getIDs() != null
                    && r.getFirst().getIDs().startsWith("flood_protection"))
                {
                    // These do not get cloned but loaded ("spawned").
                }
                ar.add(r.getFirst());
                ar.add(r.getSecond());
            }
            else {
                all.add(r.getFirst());
                all.add(r.getSecond());
            }
        }

        final Recommendation[] toClone = ar.toArray(new Recommendation[ar.size()]);
        final Recommendation[] toUse   = all.toArray(new Recommendation[all.size()]);

        // Find out whether "old" artifacts have to be removed.
        List<String> artifactIdsToRemove = new ArrayList<String>();
        for (RecommendationPairRecord rp: this.removedPairs) {
            Recommendation first  = rp.getFirst();
            Recommendation second = rp.getSecond();

            for (Recommendation recommendation: toUse) {
                if (first != null && first.getIDs().equals(recommendation.getIDs())) {
                    first = null;
                }
                if (second != null && second.getIDs().equals(recommendation.getIDs())) {
                    second = null;
                }

                if (first == null && second == null) {
                    break;
                }
            }
            if (first != null) {
                artifactIdsToRemove.add(first.getIDs());
            }
            if (second != null) {
                artifactIdsToRemove.add(second.getIDs());
            }
        }

        // Remove old artifacts, if any. Do this asychronously without much
        // feedback.
        for(final String uuid: artifactIdsToRemove) {
            removeArtifactService.remove(this.collection,
                uuid,
                config.getServerUrl(),
                config.getLocale(),
                new AsyncCallback<Collection>() {
                    public void onFailure(Throwable caught) {
                        GWT.log("RemoveArtifact (" + uuid + ") failed.");
                    }
                    public void onSuccess(Collection collection) {
                        GWT.log("RemoveArtifact succeeded");
                    }
                });
        }

        // Clone new ones, go forward.
        loadArtifactService.loadMany(
              this.collection,
              toClone,
              "waterlevel",
              config.getServerUrl(),
              config.getLocale(),
              new AsyncCallback<Artifact[]>() {
                public void onFailure(Throwable caught) {
                    GWT.log("Failure of cloning with waterlevelfactory!");
                }
                public void onSuccess(Artifact[] artifacts) {
                    GWT.log("Successfully cloned " + toClone.length +
                        " with watelevelfactory.");

                    fireStepForwardEvent(new StepForwardEvent(
                        getData(toClone, artifacts, toUse)));
                }
              } );
    }


    /**
     * Create Data and DataItem from selection (a long string with identifiers
     * to construct diff-pairs).
     *
     * @param newRecommendations "new" recommendations (did not survive a
     *        backjump).
     * @param newArtifacts artifacts cloned from newRecommendations.
     * @param oldRecommendations old recommendations that survived a backjump.
     *
     * @return dataitem with a long string with identifiers to construct
     *         diff-pairs.
     */
    protected Data[] getData(
            Recommendation[] newRecommendations,
            Artifact[] newArtifacts,
            Recommendation[] oldRecommendations)
    {
        // Construct string with info about selections.
        String dataItemString = "";
        for (int i = 0; i < newRecommendations.length; i++) {
            Recommendation r = newRecommendations[i];
            Artifact newArtifact = newArtifacts[i];
            String uuid = newArtifact.getUuid();
            r.setMasterArtifact(uuid);
            if (i>0) dataItemString += "#";
    
            dataItemString += createDataString(uuid, r.getFilter());
        }

        for (int i = 0; i < oldRecommendations.length; i++) {
            Recommendation r = oldRecommendations[i];
            String uuid = r.getIDs();
            if (dataItemString.length() > 0) dataItemString += "#";
    
            dataItemString += createDataString(uuid, r.getFilter());
        }

        DataItem item = new DefaultDataItem(dataName, dataName, dataItemString);
        return new Data[] { new DefaultData(
            dataName, null, null, new DataItem[] {item}) };
    }


    /**
     * Creates part of the String that encodes minuend or subtrahend.
     */
    protected String createDataString(String artifact, Filter filter) {
        Facet f = null;

        Map<String, List<Facet>>               outs = filter.getOuts();
        Set<Map.Entry<String, List<Facet>>> entries = outs.entrySet();

        for (Map.Entry<String, List<Facet>> entry: entries) {
            List<Facet> fs = entry.getValue();

            f = fs.get(0);
            if (f != null) {
                break;
            }
        }

        return "[" + artifact + ";" + f.getName() + ";" + f.getIndex() + "]";
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org