view flys-client/src/main/java/de/intevation/flys/client/client/ui/DatacageTwinPanel.java @ 2905:51ed89b754ae

FLYS client: Removed trailing whitespace. flys-client/trunk@4671 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Fri, 15 Jun 2012 09:35:37 +0000
parents 0b79630e3bcb
children 1e9e7b7d9f15
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);
        r.setDisplayName(parts[3]);
        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("#");
        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(
                minuend, 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.setShowHeaderContextMenu(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(GWT.getHostPageBaseURL() + MSG.removeFeature());
                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();
        String locale = config.getLocale();

        ListGridRecord[] records = differencesList.getRecords();

        List<Recommendation> ar  = new ArrayList<Recommendation>();
        List<Recommendation> all = 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.
                Recommendation firstR = r.getFirst();
                if(firstR.getIDs() != null) {
                    GWT.log("First IDs: " + firstR.getIDs() + " factory: "
                            + firstR.getFactory());
                }
                if(firstR.getIDs() != null) {
                    // These do not get cloned but loaded ("spawned").
                    firstR.setFactory("staticwkms");
                }
                else {
                    firstR.setFactory("waterlevel");
                }
                Recommendation secondR = r.getSecond();
                if(secondR.getIDs() != null) {
                    GWT.log("Second IDs: " + secondR.getIDs() + " factory: "
                            + secondR.getFactory());
                }
                if (secondR.getIDs() != null) {
                    // These do not get cloned but loaded ("spawned").
                    secondR.setFactory("staticwkms");
                }
                else {
                    secondR.setFactory("waterlevel");
                }

                ar.add(firstR);
                ar.add(secondR);
            }
            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,
                locale,
                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 (and spawn statics), go forward.
        loadArtifactService.loadMany(
            this.collection,
            toClone,
            //"staticwkms" and "waterlevel"
            null,
            locale,
            new AsyncCallback<Artifact[]>() {
                public void onFailure(Throwable caught) {
                    GWT.log("Failure of cloning with factories!");
                }
                public void onSuccess(Artifact[] artifacts) {
                    GWT.log("Successfully cloned " + toClone.length +
                        " with factories.");

                    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);
        }

        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);
        }

        // TODO some hassle could be resolved by using multiple DataItems
        // (e.g. one per pair).
        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.
     * @param artifact Artifacts UUID.
     * @param recommendation Recommendation to wrap in string.
     */
    protected String createDataString(
        String artifact,
        Recommendation recommendation)
    {
        Filter filter = recommendation.getFilter();
        Facet  f      = null;

        if(filter != 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()
                + ";"
                + recommendation.getDisplayName() + "]";
        }
        else {
            return "["
                + artifact
                + ";staticwkms;0;"
                + recommendation.getDisplayName() + "]";
        }
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org