teichmann@5835: package org.dive4elements.river.client.client.ui; felix@867: felix@867: import com.google.gwt.core.client.GWT; felix@914: import com.google.gwt.user.client.rpc.AsyncCallback; felix@867: felix@1321: import com.smartgwt.client.data.Record; felix@1321: import com.smartgwt.client.types.ListGridFieldType; felix@867: import com.smartgwt.client.widgets.Canvas; christian@4127: import com.smartgwt.client.widgets.events.ClickEvent; felix@914: import com.smartgwt.client.widgets.grid.ListGrid; felix@914: import com.smartgwt.client.widgets.grid.ListGridField; felix@914: import com.smartgwt.client.widgets.grid.ListGridRecord; felix@1321: import com.smartgwt.client.widgets.grid.events.RecordClickEvent; felix@1321: import com.smartgwt.client.widgets.grid.events.RecordClickHandler; christian@4127: import com.smartgwt.client.widgets.layout.HLayout; christian@4127: import com.smartgwt.client.widgets.layout.VLayout; felix@867: teichmann@5835: import org.dive4elements.river.client.client.Config; teichmann@5835: import org.dive4elements.river.client.client.FLYSConstants; teichmann@5835: import org.dive4elements.river.client.client.event.StepForwardEvent; teichmann@5835: import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync; teichmann@5835: import org.dive4elements.river.client.client.services.RemoveArtifactServiceAsync; teichmann@5835: import org.dive4elements.river.client.shared.model.Artifact; teichmann@5835: import org.dive4elements.river.client.shared.model.Collection; teichmann@5835: import org.dive4elements.river.client.shared.model.Data; teichmann@5835: import org.dive4elements.river.client.shared.model.DataItem; teichmann@5835: import org.dive4elements.river.client.shared.model.DataList; teichmann@5835: import org.dive4elements.river.client.shared.model.DefaultData; teichmann@5835: import org.dive4elements.river.client.shared.model.DefaultDataItem; teichmann@5835: import org.dive4elements.river.client.shared.model.Recommendation; teichmann@5835: import org.dive4elements.river.client.shared.model.Recommendation.Facet; teichmann@5835: import org.dive4elements.river.client.shared.model.Recommendation.Filter; teichmann@5835: import org.dive4elements.river.client.shared.model.User; felix@914: christian@4127: import java.util.ArrayList; christian@4127: import java.util.List; christian@4127: import java.util.Map; christian@4127: import java.util.Set; felix@867: felix@867: // TODO Probably better to branch off AbstractUIProvider. felix@914: // TODO Merge with other datacage-widget impls. felix@914: /** felix@914: * Panel containing a Grid and a "next" button. The Grid is fed by a felix@914: * DatacagePairWidget which is put in the input-helper area. felix@914: */ felix@914: public class DatacageTwinPanel felix@914: extends TextProvider { felix@867: christian@4131: private static final long serialVersionUID = 8906629596491827857L; christian@4131: felix@867: protected static FLYSConstants MSG = GWT.create(FLYSConstants.class); felix@867: felix@867: protected String dataName; felix@867: felix@867: protected User user; felix@867: felix@1321: /** ListGrid that displays user-selected pairs to build differences with. */ felix@914: protected ListGrid differencesList; felix@914: felix@1442: /** felix@1321: * List to track previously selected but now removed pairs. (Needed to felix@1321: * be able to identify artifacts that can be removed from the collection. felix@1321: */ felix@1321: protected List removedPairs = felix@1321: new ArrayList(); felix@1321: felix@1321: /** Service handle to clone and add artifacts to collection. */ felix@914: LoadArtifactServiceAsync loadArtifactService = GWT.create( teichmann@5835: org.dive4elements.river.client.client.services.LoadArtifactService.class); felix@914: felix@1321: /** Service to remove artifacts from collection. */ felix@1321: RemoveArtifactServiceAsync removeArtifactService = GWT.create( teichmann@5835: org.dive4elements.river.client.client.services.RemoveArtifactService.class); felix@867: felix@1349: felix@867: public DatacageTwinPanel(User user) { felix@867: super(); felix@867: this.user = user; felix@867: } felix@867: felix@867: felix@1298: /** felix@1298: * Remove first occurrence of "[" and "]" (if both do occur). felix@1298: * @param value String to be stripped of [] (might be null). felix@1298: * @return input string but with [ and ] removed, or input string if no felix@1298: * brackets were found. felix@1298: * @see StringUtil.unbracket felix@1298: */ felix@1298: public static final String unbracket(String value) { felix@1298: // null- guard. felix@1298: if (value == null) return value; felix@1298: felix@1298: int start = value.indexOf("["); felix@1298: int end = value.indexOf("]"); felix@1298: felix@1298: if (start < 0 || end < 0) { felix@1298: return value; felix@1298: } felix@1298: felix@1298: value = value.substring(start + 1, end); felix@1298: felix@1298: return value; felix@1298: } felix@1298: felix@1298: felix@1298: /** felix@1298: * Create a recommendation from a string representation of it. felix@1298: * @TODO describe format of input string felix@1298: * @param from string in format as shown above. felix@1298: * @return recommendation from input string. felix@1298: */ felix@1298: public Recommendation createRecommendationFromString(String from) { felix@1298: // TODO Construct "real" filter. felix@1298: String[] parts = unbracket(from).split(";"); felix@1298: Recommendation.Filter filter = new Recommendation.Filter(); felix@1298: Recommendation.Facet facet = new Recommendation.Facet( felix@1298: parts[1], felix@1298: parts[2]); felix@1298: felix@1298: List facets = new ArrayList felix@1298: (); felix@1298: facets.add(facet); felix@1298: filter.add("longitudinal_section", facets); felix@1298: Recommendation r = new Recommendation("waterlevel", parts[0], felix@1298: this.artifact.getUuid(), filter); felix@1352: r.setDisplayName(parts[3]); sascha@2905: return r; felix@1298: } felix@1298: felix@1298: felix@1298: /** felix@1298: * Add RecomendationPairRecords from input String to the ListGrid. felix@1298: */ felix@1298: public void populateGridFromString(String from){ felix@1298: // Split this string. felix@1298: // Create according recommendations and display strings. felix@1298: String[] recs = from.split("#"); felix@1298: if (recs.length % 2 != 0) return; felix@1298: for (int i = 0; i < recs.length; i+=2) { felix@1314: Recommendation minuend = felix@1314: createRecommendationFromString(recs[i+0]); felix@1314: Recommendation subtrahend = felix@1314: createRecommendationFromString(recs[i+1]); felix@1363: felix@1298: RecommendationPairRecord pr = new RecommendationPairRecord( felix@1352: minuend, subtrahend); felix@1304: // This Recommendation Pair comes from the data string and was thus felix@1304: // already cloned. felix@1304: pr.setIsAlreadyLoaded(true); felix@1298: this.differencesList.addData(pr); felix@1298: } felix@1298: } felix@1298: felix@1298: felix@1333: /** felix@1333: * Creates the graphical representation and interaction widgets for the data. felix@1333: * @param dataList the data. felix@1333: * @return graphical representation and interaction widgets for data. felix@1333: */ felix@867: @Override felix@867: public Canvas create(DataList dataList) { felix@867: GWT.log("createData()"); felix@867: felix@867: Canvas widget = createWidget(); felix@867: Canvas submit = getNextButton(); felix@867: felix@1273: VLayout layout = new VLayout(); felix@914: HLayout helperLayout = new HLayout(); felix@914: helperLayout.addMember(new DatacagePairWidget(this.artifact, felix@1308: user, "waterlevels", differencesList)); felix@867: felix@867: layout.addMember(widget); felix@867: layout.addMember(submit); felix@867: layout.setMembersMargin(10); felix@914: this.helperContainer.addMember(helperLayout); felix@867: felix@1314: // Find old data, if any, handle "diffids". felix@1543: Data data = dataList.get(0); felix@1298: this.dataName = data.getLabel(); felix@1298: for (int i = 0; i < dataList.size(); i++) { felix@1314: if (dataList.get(i) != null && dataList.get(i).getItems() != null) { felix@1298: if (dataList.get(i).getItems() != null) { felix@1314: populateGridFromString( felix@1314: dataList.get(i).getItems()[0].getStringValue()); felix@1298: } felix@1298: } felix@1298: } felix@1298: felix@867: return layout; felix@867: } felix@867: felix@867: felix@1314: /** felix@1314: * Validates the selection. felix@1321: * @return List of internationalized errror messages (if any). felix@1314: */ felix@867: @Override felix@914: public List validate() { felix@914: List errors = new ArrayList(); felix@914: if (differencesList.getRecords().length == 0) { felix@1314: errors.add(MSG.error_no_waterlevel_pair_selected()); felix@914: } felix@1307: // Check whether minuend and subtrahend are equal. felix@1314: for (ListGridRecord record: differencesList.getRecords()) { felix@1314: RecommendationPairRecord r = (RecommendationPairRecord) record; felix@1314: if (r.getFirst().equals(r.getSecond())) { felix@1314: errors.add(MSG.error_same_waterlevels_in_pair()); felix@1314: } felix@1314: } felix@914: felix@914: return errors; felix@914: } felix@914: felix@914: felix@1274: /** felix@1274: * Creates layout with grid that displays selection inside. felix@1274: */ felix@914: public Canvas createWidget() { felix@1274: VLayout layout = new VLayout(); felix@1274: differencesList = new ListGrid(); felix@914: felix@1314: differencesList.setCanEdit(false); felix@1314: differencesList.setCanSort(false); felix@1349: differencesList.setShowHeaderContextMenu(false); felix@1274: differencesList.setHeight(150); felix@1274: differencesList.setShowAllRecords(true); felix@914: felix@1274: ListGridField nameField = new ListGridField("first", "Minuend"); felix@1274: ListGridField capitalField = new ListGridField("second", "Subtrahend"); felix@1321: // Track removed rows, therefore more or less reimplement felix@1321: // setCanRecomeRecords. felix@1321: final ListGridField removeField = felix@1321: new ListGridField("_removeRecord", "Remove Record"){{ felix@1321: setType(ListGridFieldType.ICON); felix@1363: setIcon(GWT.getHostPageBaseURL() + MSG.removeFeature()); felix@1321: setCanEdit(false); felix@1321: setCanFilter(false); felix@1321: setCanSort(false); felix@1321: setCanGroupBy(false); felix@1321: setCanFreeze(false); felix@1321: setWidth(25); felix@1321: }}; felix@1321: felix@1266: differencesList.setFields(new ListGridField[] {nameField, felix@1321: capitalField, removeField}); sascha@2905: felix@1321: differencesList.addRecordClickHandler(new RecordClickHandler() { christian@4127: @Override felix@1321: public void onRecordClick(final RecordClickEvent event) { felix@1321: // Just handle remove-clicks felix@1321: if(!event.getField().getName().equals(removeField.getName())) { felix@1321: return; sascha@2905: } felix@1321: trackRemoved(event.getRecord()); felix@1321: event.getViewer().removeData(event.getRecord()); felix@1321: } felix@1321: }); felix@914: layout.addMember(differencesList); felix@914: felix@914: return layout; felix@914: } felix@914: felix@914: felix@1333: /** felix@1333: * Add record to list of removed records. felix@1333: */ felix@1321: public void trackRemoved(Record r) { felix@1321: RecommendationPairRecord pr = (RecommendationPairRecord) r; felix@1321: this.removedPairs.add(pr); felix@1321: } felix@1321: felix@1333: felix@1314: /** felix@1314: * Validates data, does nothing if invalid, otherwise clones new selected felix@1314: * waterlevels and add them to collection, forward the artifact. felix@1314: */ felix@914: @Override felix@914: public void onClick(ClickEvent e) { felix@1314: GWT.log("DatacageTwinPanel.onClick"); felix@1314: felix@914: List errors = validate(); felix@1314: if (errors != null && !errors.isEmpty()) { felix@1314: showErrors(errors); felix@1314: return; felix@1314: } felix@914: felix@914: Config config = Config.getInstance(); felix@1349: String locale = config.getLocale(); felix@1349: felix@914: ListGridRecord[] records = differencesList.getRecords(); felix@1273: felix@1307: List ar = new ArrayList(); felix@1307: List all = new ArrayList(); felix@1349: felix@914: for (ListGridRecord record : records) { felix@1298: RecommendationPairRecord r = felix@1298: (RecommendationPairRecord) record; felix@1304: // Do not add "old" recommendations. felix@1304: if (!r.isAlreadyLoaded()) { felix@1333: // Check whether one of those is a dike or similar. felix@1333: // TODO differentiate and merge: new clones, new, old. felix@1349: Recommendation firstR = r.getFirst(); felix@1349: if(firstR.getIDs() != null) { felix@1442: GWT.log("First IDs: " + firstR.getIDs() + " factory: " felix@1442: + firstR.getFactory()); felix@1349: } felix@1392: if(firstR.getIDs() != null) { felix@1333: // These do not get cloned but loaded ("spawned"). felix@1349: firstR.setFactory("staticwkms"); felix@1333: } felix@1349: else { felix@1349: firstR.setFactory("waterlevel"); felix@1349: } felix@1349: Recommendation secondR = r.getSecond(); felix@1349: if(secondR.getIDs() != null) { felix@1442: GWT.log("Second IDs: " + secondR.getIDs() + " factory: " felix@1442: + secondR.getFactory()); felix@1349: } felix@1392: if (secondR.getIDs() != null) { felix@1349: // These do not get cloned but loaded ("spawned"). felix@1349: secondR.setFactory("staticwkms"); felix@1349: } felix@1349: else { felix@1349: secondR.setFactory("waterlevel"); felix@1349: } felix@1349: felix@1349: ar.add(firstR); felix@1349: ar.add(secondR); felix@1304: } felix@1304: else { felix@1304: all.add(r.getFirst()); felix@1304: all.add(r.getSecond()); felix@1304: } felix@914: } felix@1266: felix@1304: final Recommendation[] toClone = ar.toArray(new Recommendation[ar.size()]); felix@1304: final Recommendation[] toUse = all.toArray(new Recommendation[all.size()]); felix@1321: felix@1321: // Find out whether "old" artifacts have to be removed. felix@1321: List artifactIdsToRemove = new ArrayList(); felix@1321: for (RecommendationPairRecord rp: this.removedPairs) { felix@1321: Recommendation first = rp.getFirst(); felix@1321: Recommendation second = rp.getSecond(); felix@1321: felix@1321: for (Recommendation recommendation: toUse) { felix@1321: if (first != null && first.getIDs().equals(recommendation.getIDs())) { felix@1321: first = null; felix@1321: } felix@1321: if (second != null && second.getIDs().equals(recommendation.getIDs())) { felix@1321: second = null; felix@1321: } felix@1321: felix@1321: if (first == null && second == null) { felix@1321: break; felix@1321: } felix@1321: } felix@1321: if (first != null) { felix@1321: artifactIdsToRemove.add(first.getIDs()); felix@1321: } felix@1321: if (second != null) { felix@1321: artifactIdsToRemove.add(second.getIDs()); felix@1321: } felix@1321: } felix@1321: felix@1321: // Remove old artifacts, if any. Do this asychronously without much felix@1321: // feedback. felix@1321: for(final String uuid: artifactIdsToRemove) { felix@1321: removeArtifactService.remove(this.collection, felix@1321: uuid, felix@1349: locale, felix@1321: new AsyncCallback() { christian@4127: @Override felix@1321: public void onFailure(Throwable caught) { felix@1321: GWT.log("RemoveArtifact (" + uuid + ") failed."); felix@1321: } christian@4127: @Override felix@1321: public void onSuccess(Collection collection) { felix@1321: GWT.log("RemoveArtifact succeeded"); felix@1321: } felix@1321: }); felix@1321: } felix@1321: felix@1349: // Clone new ones (and spawn statics), go forward. felix@914: loadArtifactService.loadMany( felix@1349: this.collection, felix@1349: toClone, felix@1352: //"staticwkms" and "waterlevel" felix@1349: null, felix@1349: locale, felix@1349: new AsyncCallback() { christian@4127: @Override felix@914: public void onFailure(Throwable caught) { felix@1349: GWT.log("Failure of cloning with factories!"); felix@914: } christian@4127: @Override felix@914: public void onSuccess(Artifact[] artifacts) { felix@1305: GWT.log("Successfully cloned " + toClone.length + felix@1349: " with factories."); felix@1266: felix@1266: fireStepForwardEvent(new StepForwardEvent( felix@1304: getData(toClone, artifacts, toUse))); felix@1273: } felix@1349: }); felix@914: } felix@914: felix@914: felix@1321: /** felix@1333: * Create Data and DataItem from selection (a long string with identifiers felix@1333: * to construct diff-pairs). felix@1333: * felix@1333: * @param newRecommendations "new" recommendations (did not survive a felix@1333: * backjump). felix@1333: * @param newArtifacts artifacts cloned from newRecommendations. felix@1333: * @param oldRecommendations old recommendations that survived a backjump. felix@1333: * felix@1333: * @return dataitem with a long string with identifiers to construct felix@1333: * diff-pairs. felix@1321: */ felix@1304: protected Data[] getData( felix@1304: Recommendation[] newRecommendations, felix@1304: Artifact[] newArtifacts, felix@1304: Recommendation[] oldRecommendations) felix@1304: { felix@914: // Construct string with info about selections. felix@914: String dataItemString = ""; felix@1304: for (int i = 0; i < newRecommendations.length; i++) { felix@1304: Recommendation r = newRecommendations[i]; felix@914: Artifact newArtifact = newArtifacts[i]; felix@914: String uuid = newArtifact.getUuid(); felix@914: r.setMasterArtifact(uuid); felix@914: if (i>0) dataItemString += "#"; sascha@2905: felix@1352: dataItemString += createDataString(uuid, r); felix@914: } felix@1304: felix@1304: for (int i = 0; i < oldRecommendations.length; i++) { felix@1304: Recommendation r = oldRecommendations[i]; felix@1304: String uuid = r.getIDs(); felix@1304: if (dataItemString.length() > 0) dataItemString += "#"; sascha@2905: felix@1352: dataItemString += createDataString(uuid, r); felix@1304: } felix@1304: felix@1352: // TODO some hassle could be resolved by using multiple DataItems felix@1352: // (e.g. one per pair). felix@914: DataItem item = new DefaultDataItem(dataName, dataName, dataItemString); felix@914: return new Data[] { new DefaultData( felix@914: dataName, null, null, new DataItem[] {item}) }; felix@914: } felix@914: felix@914: felix@1307: /** felix@1307: * Creates part of the String that encodes minuend or subtrahend. felix@1363: * @param artifact Artifacts UUID. felix@1363: * @param recommendation Recommendation to wrap in string. felix@1307: */ felix@1352: protected String createDataString( felix@1352: String artifact, felix@1352: Recommendation recommendation) felix@1352: { felix@1352: Filter filter = recommendation.getFilter(); felix@1352: Facet f = null; felix@914: felix@1349: if(filter != null) { felix@1349: Map> outs = filter.getOuts(); felix@1349: Set>> entries = outs.entrySet(); felix@914: felix@1349: for (Map.Entry> entry: entries) { felix@1349: List fs = entry.getValue(); felix@914: felix@1349: f = fs.get(0); felix@1349: if (f != null) { felix@1349: break; felix@1349: } felix@914: } felix@1349: felix@1363: return "[" + artifact + ";" felix@1363: + f.getName() felix@1363: + ";" felix@1363: + f.getIndex() felix@1363: + ";" felix@1363: + recommendation.getDisplayName() + "]"; felix@1349: } felix@1349: else { felix@1363: return "[" felix@1363: + artifact felix@1363: + ";staticwkms;0;" felix@1363: + recommendation.getDisplayName() + "]"; felix@914: } felix@914: } felix@867: } felix@867: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :