Mercurial > dive4elements > river
view gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractPairRecommendationPanel.java @ 8951:322b0e6298ea
Work on SINFO FlowDepth-Development
author | gernotbelger |
---|---|
date | Fri, 16 Mar 2018 18:08:38 +0100 |
parents | 8d1df8639563 |
children | 84397da33d17 |
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde * Software engineering by Intevation GmbH * * This file is Free Software under the GNU AGPL (>=v3) * and comes with ABSOLUTELY NO WARRANTY! Check out the * documentation coming with Dive4Elements River for details. */ package org.dive4elements.river.client.client.ui; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import org.dive4elements.river.client.client.Config; import org.dive4elements.river.client.client.FLYSConstants; import org.dive4elements.river.client.client.event.StepForwardEvent; import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync; import org.dive4elements.river.client.client.services.RemoveArtifactServiceAsync; import org.dive4elements.river.client.shared.model.Artifact; import org.dive4elements.river.client.shared.model.Collection; import org.dive4elements.river.client.shared.model.Data; import org.dive4elements.river.client.shared.model.DataItem; import org.dive4elements.river.client.shared.model.DataList; import org.dive4elements.river.client.shared.model.DefaultData; import org.dive4elements.river.client.shared.model.DefaultDataItem; import org.dive4elements.river.client.shared.model.Recommendation; import org.dive4elements.river.client.shared.model.Recommendation.Facet; import org.dive4elements.river.client.shared.model.Recommendation.Filter; import org.dive4elements.river.client.shared.model.User; 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.events.ClickEvent; 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.grid.events.RecordClickEvent; import com.smartgwt.client.widgets.grid.events.RecordClickHandler; import com.smartgwt.client.widgets.layout.VLayout; // 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 abstract class AbstractPairRecommendationPanel extends TextProvider { /** * Allows for abstraction on how to handle/serialize the recommendation and the used factories. * Basically this allows to tweak the factory that is delivered from the datacage to be replaced by a specific one... * * @author Gernot Belger */ public static interface IRecommendationInfo { String getFactory(String originalFactory); /** * Separate factory for the 'createDataString' method, because in the case of waterlevels. See HOTFIX/FIXME there. * @param recommendation */ String getDataStringFactory(Recommendation recommendation); /** * Set factory of recommendation such that the correct artifacts will * be cloned for difference calculations. */ void adjustRecommendation(Recommendation recommendation); } public static interface IValidator { List<String> validate(ListGrid differencesList, FLYSConstants msgProvider); } private static final long serialVersionUID = 8906629596491827857L; // FIXME: why? we hide the field of the super class with exactly the same thing... private static FLYSConstants MSG_PROVIDER = GWT.create(FLYSConstants.class); private String dataName; private final User user; /** ListGrid that displays user-selected pairs to build differences with. */ private 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. */ private final List<RecommendationPairRecord> removedPairs = new ArrayList<RecommendationPairRecord>(); /** Service handle to clone and add artifacts to collection. */ private final LoadArtifactServiceAsync loadArtifactService = GWT.create( org.dive4elements.river.client.client.services.LoadArtifactService.class); /** Service to remove artifacts from collection. */ private final RemoveArtifactServiceAsync removeArtifactService = GWT.create( org.dive4elements.river.client.client.services.RemoveArtifactService.class); private final IRecommendationInfo leftInfo; private final IRecommendationInfo rightInfo; private final IValidator validator; /** * @param Validates the content of this form when the user clicks 'apply' * @param leftInfo Delegate for handling the left part of the recommendation-pair * @param rightInfo Delegate for handling the right part of the recommendation-pair */ public AbstractPairRecommendationPanel(final User user, final IValidator validator, final IRecommendationInfo leftInfo, final IRecommendationInfo rightInfo ) { this.user = user; this.validator = validator; this.leftInfo = leftInfo; this.rightInfo = rightInfo; } // FIXME: better than copy/pasting the MSG field into every sub-class but not really nice yet. protected final static FLYSConstants msg() { return MSG_PROVIDER; } /** * 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 */ // FIXME: check if this is the same as STringUItils#unbracket private static final String unbracket(final String value) { // null- guard. if (value == null) return value; final int start = value.indexOf("["); final int end = value.indexOf("]"); if (start < 0 || end < 0) { return value; } return value.substring(start + 1, end); } /** * Create a recommendation from a string representation of it. * @param from string in format as shown above. * @param leftInfo2 * @return recommendation from input string. */ private Recommendation createRecommendationFromString(final String from, final IRecommendationInfo info) { // TODO Construct "real" filter. final String[] parts = unbracket(from).split(";"); final Recommendation.Filter filter = new Recommendation.Filter(); final Recommendation.Facet facet = new Recommendation.Facet( parts[1], parts[2]); final List<Recommendation.Facet> facets = new ArrayList<Recommendation.Facet>(); facets.add(facet); filter.add("longitudinal_section", facets); final String factory = info.getFactory( parts[1] ); final Recommendation r = new Recommendation(factory, parts[0], this.artifact.getUuid(), filter); r.setDisplayName(parts[3]); return r; } /** * Add RecomendationPairRecords from input String to the ListGrid. */ private void populateGridFromString(final String from){ // Split this string. // Create according recommendations and display strings. final String[] recs = from.split("#"); if (recs.length % 2 != 0) return; for (int i = 0; i < recs.length; i+=2) { final Recommendation minuend = createRecommendationFromString(recs[i+0], this.leftInfo); final Recommendation subtrahend = createRecommendationFromString(recs[i+1], this.rightInfo); final 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 final Canvas create(final DataList dataList) { final Canvas widget = createWidget(); final Canvas canvas = createChooserWidgets(widget, dataList, this.user, this.differencesList); populateGrid(dataList); return canvas; } /** * Creates the individual parts of the input-helper area ('Eingabeunterstützung') for choosing the content of this widget. */ protected abstract Canvas createChooserWidgets(final Canvas widget, final DataList dataList, final User auser, final ListGrid diffList); private void populateGrid(final DataList dataList) { final 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()); } } } } @Override public final List<String> validate() { return this.validator.validate(this.differencesList, MSG_PROVIDER); } /** * Creates layout with grid that displays selection inside. */ protected final Canvas createWidget() { final VLayout layout = new VLayout(); this.differencesList = new ListGrid(); this.differencesList.setCanEdit(false); this.differencesList.setCanSort(false); this.differencesList.setShowHeaderContextMenu(false); this.differencesList.setHeight(150); this.differencesList.setShowAllRecords(true); final ListGridField nameField = new ListGridField("first", "Minuend"); final 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); }}; this.differencesList.setFields(new ListGridField[] {nameField, capitalField, removeField}); this.differencesList.addRecordClickHandler(new RecordClickHandler() { @Override 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(this.differencesList); return layout; } /** * Add record to list of removed records. */ protected final void trackRemoved(final Record r) { final 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(final ClickEvent e) { GWT.log("AbstractPairRecommendationPanel.onClick"); final List<String> errors = validate(); if (errors != null && !errors.isEmpty()) { showErrors(errors); return; } final Config config = Config.getInstance(); final String locale = config.getLocale(); final ListGridRecord[] records = this.differencesList.getRecords(); final List<Recommendation> ar = new ArrayList<Recommendation>(); final List<Recommendation> all = new ArrayList<Recommendation>(); for (final ListGridRecord record : records) { final 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. final Recommendation firstR = r.getFirst(); this.leftInfo.adjustRecommendation(firstR); final Recommendation secondR = r.getSecond(); this.rightInfo.adjustRecommendation(secondR); 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. final List<String> artifactIdsToRemove = new ArrayList<String>(); for (final RecommendationPairRecord rp: this.removedPairs) { Recommendation first = rp.getFirst(); Recommendation second = rp.getSecond(); for (final 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) { this.removeArtifactService.remove(this.collection, uuid, locale, new AsyncCallback<Collection>() { @Override public void onFailure(final Throwable caught) { GWT.log("RemoveArtifact (" + uuid + ") failed."); } @Override public void onSuccess(final Collection coll) { GWT.log("RemoveArtifact succeeded"); } }); } // Clone new ones (and spawn statics), go forward. this.parameterList.lockUI(); this.loadArtifactService.loadMany( this.collection, toClone, //"staticwkms" and "waterlevel" null, locale, new AsyncCallback<Artifact[]>() { @Override public void onFailure(final Throwable caught) { caught.printStackTrace(); GWT.log("Failure of cloning with factories!"); AbstractPairRecommendationPanel.this.parameterList.unlockUI(); } @Override public void onSuccess(final Artifact[] artifacts) { GWT.log("Successfully cloned " + toClone.length + " with factories."); fireStepForwardEvent(new StepForwardEvent( getData(toClone, artifacts, toUse))); AbstractPairRecommendationPanel.this.parameterList.unlockUI(); } }); } /** * 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 final Data[] getData( final Recommendation[] newRecommendations, final Artifact[] newArtifacts, final Recommendation[] oldRecommendations) { // Construct string with info about selections. String dataItemString = ""; for (int i = 0; i < newRecommendations.length; i++) { final Recommendation r = newRecommendations[i]; final Artifact newArtifact = newArtifacts[i]; final String uuid = newArtifact.getUuid(); r.setMasterArtifact(uuid); if (i>0) dataItemString += "#"; // REMARK: ugly, but we know that the recommandations comes in left/right pairs. final IRecommendationInfo info = i % 2 == 0 ? this.leftInfo : this.rightInfo; dataItemString += createDataString(uuid, r, info); } for (int i = 0; i < oldRecommendations.length; i++) { final Recommendation r = oldRecommendations[i]; final String uuid = r.getIDs(); if (dataItemString.length() > 0) dataItemString += "#"; // REMARK: ugly, but we know that the recommandations comes in left/right pairs. final IRecommendationInfo info = i % 2 == 0 ? this.leftInfo : this.rightInfo; dataItemString += createDataString(uuid, r, info); } // TODO some hassle could be resolved by using multiple DataItems // (e.g. one per pair). final DataItem item = new DefaultDataItem(this.dataName, this.dataName, dataItemString); return new Data[] { new DefaultData( this.dataName, null, null, new DataItem[] {item}) }; } /** * Creates part of the String that encodes minuend or subtrahend. * @param recommendation Recommendation to wrap in string. * @param info Provides the factory to encode. */ protected static final String createDataString(final String artifactUuid, final Recommendation recommendation, final IRecommendationInfo info) { final String factory = info.getDataStringFactory( recommendation ); final Filter filter = recommendation.getFilter(); Facet f = null; if(filter != null) { final Map<String, List<Facet>> outs = filter.getOuts(); final Set<Map.Entry<String, List<Facet>>> entries = outs.entrySet(); for (final Map.Entry<String, List<Facet>> entry: entries) { final List<Facet> fs = entry.getValue(); f = fs.get(0); if (f != null) { break; } } return "[" + artifactUuid + ";" + f.getName() + ";" + f.getIndex() + ";" + recommendation.getDisplayName() + "]"; } return "[" + artifactUuid + ";" + factory + ";0;" + recommendation.getDisplayName() + "]"; } }