# HG changeset patch # User gernotbelger # Date 1516296750 -3600 # Node ID 8f6d6d26e96f0985b95cd99b1616b3a8eb7862c0 # Parent 13650d8a2373b27e49474ca16c3051858bf52714 Refaktored the DatacageTwinPanel so it is reusable. diff -r 13650d8a2373 -r 8f6d6d26e96f artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelPairSelectState.java --- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelPairSelectState.java Thu Jan 18 18:26:30 2018 +0100 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelPairSelectState.java Thu Jan 18 18:32:30 2018 +0100 @@ -48,7 +48,7 @@ /** Specify to display a datacage_twin_panel. */ @Override protected String getUIProvider() { - return "datacage_twin_panel"; + return "waterlevel_twin_panel"; } diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractPairRecommendationPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractPairRecommendationPanel.java Thu Jan 18 18:32:30 2018 +0100 @@ -0,0 +1,508 @@ +/* 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 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; + +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 java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +// 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. + * @author Gernot Belger + * + */ + public static interface IRecommendationInfo { + + String getFactory(); + + /** + * Separate factory for the 'createDataString' method, because in the case of waterlevels. See HOTFIX/FIXME there. + */ + String getDataStringFactory(); + + /** + * Set factory of recommendation such that the correct artifacts will + * be cloned for difference calculations. + */ + void adjustRecommendation(Recommendation recommendation); + } + + public static interface IValidator + { + List 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 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 List removedPairs = + new ArrayList(); + + /** Service handle to clone and add artifacts to collection. */ + private LoadArtifactServiceAsync loadArtifactService = GWT.create( + org.dive4elements.river.client.client.services.LoadArtifactService.class); + + /** Service to remove artifacts from collection. */ + private RemoveArtifactServiceAsync removeArtifactService = GWT.create( + org.dive4elements.river.client.client.services.RemoveArtifactService.class); + + private IRecommendationInfo leftInfo; + + private IRecommendationInfo rightInfo; + + private 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, 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(String value) { + // null- guard. + if (value == null) return value; + + int start = value.indexOf("["); + 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. + String[] parts = unbracket(from).split(";"); + Recommendation.Filter filter = new Recommendation.Filter(); + Recommendation.Facet facet = new Recommendation.Facet( + parts[1], + parts[2]); + + List facets = new ArrayList(); + facets.add(facet); + filter.add("longitudinal_section", facets); + + final String factory = info.getFactory( ); + + 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(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], leftInfo); + Recommendation subtrahend = + createRecommendationFromString(recs[i+1], rightInfo); + + 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(DataList dataList) { + + final Canvas widget = createWidget(); + + final Canvas canvas = createChooserWidgets(widget, dataList, user, 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(DataList dataList) { + 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 validate() { + return validator.validate(differencesList, MSG_PROVIDER); + } + + /** + * Creates layout with grid that displays selection inside. + */ + protected final 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() { + @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(differencesList); + + return layout; + } + + + /** + * Add record to list of removed records. + */ + protected final 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("AbstractPairRecommendationPanel.onClick"); + + List errors = validate(); + if (errors != null && !errors.isEmpty()) { + showErrors(errors); + return; + } + + Config config = Config.getInstance(); + String locale = config.getLocale(); + + ListGridRecord[] records = differencesList.getRecords(); + + List ar = new ArrayList(); + List all = new ArrayList(); + + 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(); + leftInfo.adjustRecommendation(firstR); + + Recommendation secondR = r.getSecond(); + 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. + List artifactIdsToRemove = new ArrayList(); + 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() { + @Override + public void onFailure(Throwable caught) { + GWT.log("RemoveArtifact (" + uuid + ") failed."); + } + @Override + public void onSuccess(Collection coll) { + GWT.log("RemoveArtifact succeeded"); + } + }); + } + + // Clone new ones (and spawn statics), go forward. + parameterList.lockUI(); + loadArtifactService.loadMany( + this.collection, + toClone, + //"staticwkms" and "waterlevel" + null, + locale, + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + caught.printStackTrace(); + GWT.log("Failure of cloning with factories!"); + parameterList.unlockUI(); + } + @Override + public void onSuccess(Artifact[] artifacts) { + GWT.log("Successfully cloned " + toClone.length + + " with factories."); + + fireStepForwardEvent(new StepForwardEvent( + getData(toClone, artifacts, toUse))); + 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( + 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 += "#"; + + // REMARK: ugly, but we know that the recommandations comes in left/right pairs. + final IRecommendationInfo info = i % 2 == 0 ? leftInfo : rightInfo; + + dataItemString += createDataString(uuid, r, info); + } + + for (int i = 0; i < oldRecommendations.length; i++) { + Recommendation r = oldRecommendations[i]; + 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 ? leftInfo : rightInfo; + + dataItemString += createDataString(uuid, r, info); + } + + // 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 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(); + + Filter filter = recommendation.getFilter(); + Facet f = null; + + if(filter != null) { + Map> outs = filter.getOuts(); + Set>> entries = outs.entrySet(); + + for (Map.Entry> entry: entries) { + List 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() + "]"; + } +} \ No newline at end of file diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractUIProvider.java --- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractUIProvider.java Thu Jan 18 18:26:30 2018 +0100 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractUIProvider.java Thu Jan 18 18:32:30 2018 +0100 @@ -307,12 +307,14 @@ return null; } - + /** + * Validates the selection. + * @return List of internationalized errror messages (if any). + */ public List validate() { return new ArrayList(); // FIXME: What's this? } - /** Create simple DefaultData with single DataItem inside. */ public static DefaultData createDataArray(String name, String value) { DataItem item = new DefaultDataItem( diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacagePairWidget.java --- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacagePairWidget.java Thu Jan 18 18:26:30 2018 +0100 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacagePairWidget.java Thu Jan 18 18:32:30 2018 +0100 @@ -49,12 +49,14 @@ * * @param artifact Artifact to query datacage with. * @param user User to query datacage with. - * @param outs outs to query datacage with. + * @param leftOuts outs to query the left datacage with. + * @param rightOuts outs to query the right datacage with. * @param grid Grid into which to insert selection of pairs. */ public DatacagePairWidget(Artifact artifact, User user, - String outs, + String leftOuts, + String rightOuts, ListGrid grid) { this.grid = grid; @@ -62,13 +64,13 @@ firstDatacageWidget = new DatacageWidget( artifact, user, - outs, + leftOuts, "load-system:true", false); secondDatacageWidget = new DatacageWidget( artifact, user, - outs, + rightOuts, "load-system:true", false); firstDatacageWidget.setIsMutliSelectable(false); diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageTwinPanel.java --- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageTwinPanel.java Thu Jan 18 18:26:30 2018 +0100 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageTwinPanel.java Thu Jan 18 18:32:30 2018 +0100 @@ -8,511 +8,56 @@ package org.dive4elements.river.client.client.ui; +import org.dive4elements.river.client.shared.model.DataList; +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.HLayout; import com.smartgwt.client.widgets.layout.VLayout; -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 java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -// 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 + * A {@link AbstractPairRecommendationPanel} that uses a 'TwinDatacage' in the help-input area. * DatacagePairWidget which is put in the input-helper area. */ -public class DatacageTwinPanel -extends TextProvider { - - private static final long serialVersionUID = 8906629596491827857L; - - 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 removedPairs = - new ArrayList(); - - /** Service handle to clone and add artifacts to collection. */ - LoadArtifactServiceAsync loadArtifactService = GWT.create( - org.dive4elements.river.client.client.services.LoadArtifactService.class); - - /** Service to remove artifacts from collection. */ - RemoveArtifactServiceAsync removeArtifactService = GWT.create( - org.dive4elements.river.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); +public abstract class DatacageTwinPanel +extends AbstractPairRecommendationPanel { - return value; - } - - - /** - * Create a recommendation from a string representation of it. - * @param from string in format as shown above. - * @return recommendation from input string. - */ - public Recommendation createRecommendationFromString(String from, String factory) { - // 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 facets = new ArrayList - (); - facets.add(facet); - filter.add("longitudinal_section", facets); - Recommendation r = new Recommendation(factory, parts[0], - this.artifact.getUuid(), filter); - r.setDisplayName(parts[3]); - return r; - } - + private IDatacageTwinPanelInfo leftInfo; + private IDatacageTwinPanelInfo rightInfo; - /** - * Add RecomendationPairRecords from input String to the ListGrid. - */ - public void populateGridFromString(String from, String factory){ - // 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], factory); - Recommendation subtrahend = - createRecommendationFromString(recs[i+1], factory); + public static interface IDatacageTwinPanelInfo extends IRecommendationInfo + { + String getOuts(); + } + + public DatacageTwinPanel(final User user, IValidator validator, final IDatacageTwinPanelInfo leftInfo, final IDatacageTwinPanelInfo rightInfo ) { + super(user, validator, leftInfo, rightInfo); - 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. - */ + this.leftInfo = leftInfo; + this.rightInfo = rightInfo; + } + @Override - public Canvas create(DataList dataList) { + protected final Canvas createChooserWidgets(final Canvas widget, final DataList dataList, final User user, final ListGrid differencesList) { GWT.log("createData()"); - Canvas widget = createWidget(); Canvas submit = getNextButton(); VLayout layout = new VLayout(); HLayout helperLayout = new HLayout(); - helperLayout.addMember(new DatacagePairWidget(this.artifact, - user, "winfo_diff_twin_panel", differencesList)); + + final String leftOuts = leftInfo.getOuts(); + final String rightOuts = rightInfo.getOuts(); + + helperLayout.addMember(new DatacagePairWidget(this.artifact, user, leftOuts, rightOuts, differencesList)); layout.addMember(widget); layout.addMember(submit); layout.setMembersMargin(10); this.helperContainer.addMember(helperLayout); - populateGrid(dataList, "waterlevel"); - return layout; - } - - protected void populateGrid(DataList dataList, String factory) { - 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(), - factory); - } - } - } - } - - - /** - * Validates the selection. - * @return List of internationalized errror messages (if any). - */ - @Override - public List validate() { - List errors = new ArrayList(); - 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() { - @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(differencesList); - return layout; } - - - /** - * Add record to list of removed records. - */ - public void trackRemoved(Record r) { - RecommendationPairRecord pr = (RecommendationPairRecord) r; - this.removedPairs.add(pr); - } - - /** - * Set factory of recommendation such that the correct artifacts will - * be cloned for difference calculations. - */ - public void adjustRecommendation(Recommendation recommendation) { - // XXX: THIS IS AN EVIL HACK TO MAKE W-DIFFERENCES WORK AGAIN! - // TODO: Throw all this code away and do it with server side recommendations! - recommendation.setTargetOut("w_differences"); - - if (recommendation.getIDs() != null) { - GWT.log("Setting staticwkms factory for rec with ID " - + recommendation.getIDs()); - recommendation.setFactory("staticwkms"); - } - /* - // So far, we do not need to rewrite the factory anymore, - // except for staticwkms; probably other cases will pop up later. - else if (recommendation.getFactory().equals("winfo")) { - GWT.log("Setting waterlevel factory for a winfo rec."); - recommendation.setFactory("waterlevel"); - } - */ - else { - GWT.log("Leave rec. id " + recommendation.getIDs() + ", factory " - + recommendation.getFactory() + " untouched."); - } - } - - /** - * 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 errors = validate(); - if (errors != null && !errors.isEmpty()) { - showErrors(errors); - return; - } - - Config config = Config.getInstance(); - String locale = config.getLocale(); - - ListGridRecord[] records = differencesList.getRecords(); - - List ar = new ArrayList(); - List all = new ArrayList(); - - 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(); - adjustRecommendation(firstR); - - Recommendation secondR = r.getSecond(); - 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. - List artifactIdsToRemove = new ArrayList(); - 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() { - @Override - public void onFailure(Throwable caught) { - GWT.log("RemoveArtifact (" + uuid + ") failed."); - } - @Override - public void onSuccess(Collection collection) { - GWT.log("RemoveArtifact succeeded"); - } - }); - } - - // Clone new ones (and spawn statics), go forward. - parameterList.lockUI(); - loadArtifactService.loadMany( - this.collection, - toClone, - //"staticwkms" and "waterlevel" - null, - locale, - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - GWT.log("Failure of cloning with factories!"); - parameterList.unlockUI(); - } - @Override - public void onSuccess(Artifact[] artifacts) { - GWT.log("Successfully cloned " + toClone.length + - " with factories."); - - fireStepForwardEvent(new StepForwardEvent( - getData(toClone, artifacts, toUse))); - 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 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}) }; - } - - - protected String createDataString(String artifact, Recommendation recommendation) { - return createDataString(artifact, recommendation, "staticwkms"); - } - - /** - * Creates part of the String that encodes minuend or subtrahend. - * @param artifact Artifacts UUID. - * @param recommendation Recommendation to wrap in string. - * @param factory The factory to encode. - */ - protected String createDataString( - String artifact, - Recommendation recommendation, - String factory) - { - Filter filter = recommendation.getFilter(); - Facet f = null; - - if(filter != null) { - Map> outs = filter.getOuts(); - Set>> entries = outs.entrySet(); - - for (Map.Entry> entry: entries) { - List fs = entry.getValue(); - - f = fs.get(0); - if (f != null) { - break; - } - } - - return "[" + artifact + ";" - + f.getName() - + ";" - + f.getIndex() - + ";" - + recommendation.getDisplayName() + "]"; - } - else { - return "[" - + artifact - + ";" + factory + ";0;" - + recommendation.getDisplayName() + "]"; - } - } -} -// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : +} \ No newline at end of file diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DefaultDatacageTwinPanelInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DefaultDatacageTwinPanelInfo.java Thu Jan 18 18:32:30 2018 +0100 @@ -0,0 +1,47 @@ +/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde + * Software engineering by + * Björnsen Beratende Ingenieure GmbH + * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt + * + * 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 org.dive4elements.river.client.client.ui.DatacageTwinPanel.IDatacageTwinPanelInfo; +import org.dive4elements.river.client.shared.model.Recommendation; + +/** + * @author Gernot Belger + */ +public final class DefaultDatacageTwinPanelInfo implements IDatacageTwinPanelInfo { + + private String factory; + private String outs; + + public DefaultDatacageTwinPanelInfo(final String factory, final String outs) { + this.factory = factory; + this.outs = outs; + } + + @Override + public String getFactory() { + return factory; + } + + @Override + public String getDataStringFactory() { + return factory; + } + + @Override + public void adjustRecommendation(Recommendation recommendation) { + recommendation.setFactory(factory); + } + + @Override + public String getOuts() { + return outs; + } +} \ No newline at end of file diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelRecommandationInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelRecommandationInfo.java Thu Jan 18 18:32:30 2018 +0100 @@ -0,0 +1,69 @@ +/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde + * Software engineering by + * Björnsen Beratende Ingenieure GmbH + * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt + * + * 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 org.dive4elements.river.client.client.ui.DatacageTwinPanel.IDatacageTwinPanelInfo; +import org.dive4elements.river.client.shared.model.Recommendation; + +import com.google.gwt.core.client.GWT; + +/** + * @author Gernot Belger + */ +public final class WaterlevelRecommandationInfo implements IDatacageTwinPanelInfo { + + private String outs; + + public WaterlevelRecommandationInfo(String outs ) { + this.outs = outs; + } + + @Override + public String getFactory() { + // FIXME: why are the factory here and the one used in createDataString different? + // Probably also because of the 'throw all this code away comment' + return "waterlevel"; + } + + @Override + public String getDataStringFactory() { + return "staticwkms"; + } + + @Override + public void adjustRecommendation(Recommendation recommendation) { + // XXX: THIS IS AN EVIL HACK TO MAKE W-DIFFERENCES WORK AGAIN! + // TODO: Throw all this code away and do it with server side recommendations! + recommendation.setTargetOut("w_differences"); + + if (recommendation.getIDs() != null) { + GWT.log("Setting staticwkms factory for rec with ID " + + recommendation.getIDs()); + recommendation.setFactory("staticwkms"); + } + /* + // So far, we do not need to rewrite the factory anymore, + // except for staticwkms; probably other cases will pop up later. + else if (recommendation.getFactory().equals("winfo")) { + GWT.log("Setting waterlevel factory for a winfo rec."); + recommendation.setFactory("waterlevel"); + } + */ + else { + GWT.log("Leave rec. id " + recommendation.getIDs() + ", factory " + + recommendation.getFactory() + " untouched."); + } + } + + @Override + public String getOuts() { + return outs; + } +} \ No newline at end of file diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelTwinPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelTwinPanel.java Thu Jan 18 18:32:30 2018 +0100 @@ -0,0 +1,25 @@ +/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde + * Software engineering by + * Björnsen Beratende Ingenieure GmbH + * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt + * + * 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 org.dive4elements.river.client.shared.model.User; + +/** + * A DatacageTwinPanel implementation for W-INFO Differences: choose two waterlevels + * + * @author Gernot Belger + */ +public class WaterlevelTwinPanel +extends DatacageTwinPanel { + + public WaterlevelTwinPanel(final User user) { + super(user, new WaterlevelTwinPanelValidator(), new WaterlevelRecommandationInfo("winfo_diff_twin_panel"), new WaterlevelRecommandationInfo("winfo_diff_twin_panel") ); + } +} \ No newline at end of file diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelTwinPanelValidator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelTwinPanelValidator.java Thu Jan 18 18:32:30 2018 +0100 @@ -0,0 +1,48 @@ +/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde + * Software engineering by + * Björnsen Beratende Ingenieure GmbH + * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt + * + * 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 org.dive4elements.river.client.client.FLYSConstants; +import org.dive4elements.river.client.client.ui.AbstractPairRecommendationPanel.IValidator; + +import com.smartgwt.client.widgets.grid.ListGrid; +import com.smartgwt.client.widgets.grid.ListGridRecord; + +/** + * Contains the old code from the validate-method of the DatacageTwinPanel. + * + * @author Gernot Belger + */ +public final class WaterlevelTwinPanelValidator implements IValidator { + + @Override + public List validate(final ListGrid differencesList, final FLYSConstants msgProvider) { + + final List errors = new ArrayList(); + if (differencesList.getRecords().length == 0) { + // FIXME: waterlevel dependent! This will lead to a bad error message in English, for M-Info/Bed-Differences calculation + errors.add(msgProvider.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())) { + // FIXME: this is still waterlevel specific! + // TODO: delegate validation to specific implementations + errors.add(msgProvider.error_same_waterlevels_in_pair()); + } + } + + return errors; + } +} \ No newline at end of file diff -r 13650d8a2373 -r 8f6d6d26e96f gwt-client/src/main/java/org/dive4elements/river/client/client/ui/minfo/BedHeightsDatacagePanel.java --- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/minfo/BedHeightsDatacagePanel.java Thu Jan 18 18:26:30 2018 +0100 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/minfo/BedHeightsDatacagePanel.java Thu Jan 18 18:32:30 2018 +0100 @@ -8,83 +8,51 @@ package org.dive4elements.river.client.client.ui.minfo; +import java.util.List; + +import org.dive4elements.river.client.client.ui.AbstractPairRecommendationPanel; +import org.dive4elements.river.client.client.ui.DatacageWidget; +import org.dive4elements.river.client.client.ui.DefaultDatacageTwinPanelInfo; +import org.dive4elements.river.client.client.ui.RecommendationPairRecord; +import org.dive4elements.river.client.client.ui.WaterlevelTwinPanelValidator; +import org.dive4elements.river.client.shared.model.DataList; +import org.dive4elements.river.client.shared.model.ToLoad; +import org.dive4elements.river.client.shared.model.User; + import com.google.gwt.core.client.GWT; - import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.Button; import com.smartgwt.client.widgets.Canvas; - import com.smartgwt.client.widgets.events.ClickEvent; import com.smartgwt.client.widgets.events.ClickHandler; - +import com.smartgwt.client.widgets.grid.ListGrid; import com.smartgwt.client.widgets.layout.VLayout; import com.smartgwt.client.widgets.tree.TreeNode; -import org.dive4elements.river.client.client.FLYSConstants; - -import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync; -import org.dive4elements.river.client.client.services.RemoveArtifactServiceAsync; - -import org.dive4elements.river.client.client.ui.DatacageTwinPanel; -import org.dive4elements.river.client.client.ui.DatacageWidget; -import org.dive4elements.river.client.client.ui.RecommendationPairRecord; - -import org.dive4elements.river.client.shared.model.DataList; -import org.dive4elements.river.client.shared.model.ToLoad; - -import org.dive4elements.river.client.shared.model.Recommendation; -import org.dive4elements.river.client.shared.model.User; - -import java.util.ArrayList; -import java.util.List; - -// TODO Probably better to branch off AbstractUIProvider. public class BedHeightsDatacagePanel -extends DatacageTwinPanel { - - protected static FLYSConstants MSG = GWT.create(FLYSConstants.class); - - /** - * 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 removedPairs = - new ArrayList(); - - /** Service handle to clone and add artifacts to collection. */ - LoadArtifactServiceAsync loadArtifactService = GWT.create( - org.dive4elements.river.client.client.services.LoadArtifactService.class); - - /** Service to remove artifacts from collection. */ - RemoveArtifactServiceAsync removeArtifactService = GWT.create( - org.dive4elements.river.client.client.services.RemoveArtifactService.class); - - protected DatacageWidget datacage; +extends AbstractPairRecommendationPanel { public BedHeightsDatacagePanel(User user) { - super(user); + // FIXME: This will lead to a bad error message in English (i.e. contains something about waterlevels), for M-Info/Bed-Differences calculation + // BUT: this is the behavior of 3.2.1 (because of sloppy derivation), so we do not change it now + super(user, new WaterlevelTwinPanelValidator(), new DefaultDatacageTwinPanelInfo("bedheight", null), new DefaultDatacageTwinPanelInfo("bedheight", null) ); } - /** - * 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) { + protected Canvas createChooserWidgets(final Canvas widget, final DataList dataList, final User user, final ListGrid differencesList) { GWT.log("createData()"); - Canvas widget = createWidget(); Canvas submit = getNextButton(); - datacage = new DatacageWidget( + + final DatacageWidget datacage = new DatacageWidget( this.artifact, user, "minfo_diff_panel", "load-system:true", false); - Button plusBtn = new Button(MSG.datacage_add_pair()); + Button plusBtn = new Button(msg().datacage_add_pair()); plusBtn.setAutoFit(true); plusBtn.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - plusClicked(); + plusClicked(datacage, differencesList); } }); @@ -98,29 +66,19 @@ layout.setMembersMargin(10); this.helperContainer.addMember(helperLayout); - populateGrid(dataList, "bedheight"); - return layout; } - public void adjustRecommendation(Recommendation recommendation) { - recommendation.setFactory("bedheight"); - } - - @Override - protected String createDataString(String artifact, Recommendation recommendation) { - return createDataString(artifact, recommendation, "bedheight"); - } - /** * Callback for add-button. * Fires to load for every selected element and handler. + * @param differencesList */ - public void plusClicked() { + protected final static void plusClicked( final DatacageWidget datacage, ListGrid differencesList ) { List selection = datacage.getPlainSelection(); if (selection == null || selection.isEmpty()) { - SC.say(MSG.warning()); + SC.say(msg().warning()); return; }