comparison gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageTwinPanel.java @ 8852:8f6d6d26e96f

Refaktored the DatacageTwinPanel so it is reusable.
author gernotbelger
date Thu, 18 Jan 2018 18:32:30 +0100
parents 36f52c80b7ac
children 28df64078f27
comparison
equal deleted inserted replaced
8851:13650d8a2373 8852:8f6d6d26e96f
6 * documentation coming with Dive4Elements River for details. 6 * documentation coming with Dive4Elements River for details.
7 */ 7 */
8 8
9 package org.dive4elements.river.client.client.ui; 9 package org.dive4elements.river.client.client.ui;
10 10
11 import org.dive4elements.river.client.shared.model.DataList;
12 import org.dive4elements.river.client.shared.model.User;
13
11 import com.google.gwt.core.client.GWT; 14 import com.google.gwt.core.client.GWT;
12 import com.google.gwt.user.client.rpc.AsyncCallback;
13
14 import com.smartgwt.client.data.Record;
15 import com.smartgwt.client.types.ListGridFieldType;
16 import com.smartgwt.client.widgets.Canvas; 15 import com.smartgwt.client.widgets.Canvas;
17 import com.smartgwt.client.widgets.events.ClickEvent;
18 import com.smartgwt.client.widgets.grid.ListGrid; 16 import com.smartgwt.client.widgets.grid.ListGrid;
19 import com.smartgwt.client.widgets.grid.ListGridField;
20 import com.smartgwt.client.widgets.grid.ListGridRecord;
21 import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
22 import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
23 import com.smartgwt.client.widgets.layout.HLayout; 17 import com.smartgwt.client.widgets.layout.HLayout;
24 import com.smartgwt.client.widgets.layout.VLayout; 18 import com.smartgwt.client.widgets.layout.VLayout;
25 19
26 import org.dive4elements.river.client.client.Config;
27 import org.dive4elements.river.client.client.FLYSConstants;
28 import org.dive4elements.river.client.client.event.StepForwardEvent;
29 import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync;
30 import org.dive4elements.river.client.client.services.RemoveArtifactServiceAsync;
31 import org.dive4elements.river.client.shared.model.Artifact;
32 import org.dive4elements.river.client.shared.model.Collection;
33 import org.dive4elements.river.client.shared.model.Data;
34 import org.dive4elements.river.client.shared.model.DataItem;
35 import org.dive4elements.river.client.shared.model.DataList;
36 import org.dive4elements.river.client.shared.model.DefaultData;
37 import org.dive4elements.river.client.shared.model.DefaultDataItem;
38 import org.dive4elements.river.client.shared.model.Recommendation;
39 import org.dive4elements.river.client.shared.model.Recommendation.Facet;
40 import org.dive4elements.river.client.shared.model.Recommendation.Filter;
41 import org.dive4elements.river.client.shared.model.User;
42
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47
48 // TODO Probably better to branch off AbstractUIProvider.
49 // TODO Merge with other datacage-widget impls.
50 /** 20 /**
51 * Panel containing a Grid and a "next" button. The Grid is fed by a 21 * A {@link AbstractPairRecommendationPanel} that uses a 'TwinDatacage' in the help-input area.
52 * DatacagePairWidget which is put in the input-helper area. 22 * DatacagePairWidget which is put in the input-helper area.
53 */ 23 */
54 public class DatacageTwinPanel 24 public abstract class DatacageTwinPanel
55 extends TextProvider { 25 extends AbstractPairRecommendationPanel {
56 26
57 private static final long serialVersionUID = 8906629596491827857L; 27 private IDatacageTwinPanelInfo leftInfo;
28 private IDatacageTwinPanelInfo rightInfo;
58 29
59 protected static FLYSConstants MSG = GWT.create(FLYSConstants.class); 30 public static interface IDatacageTwinPanelInfo extends IRecommendationInfo
31 {
32 String getOuts();
33 }
34
35 public DatacageTwinPanel(final User user, IValidator validator, final IDatacageTwinPanelInfo leftInfo, final IDatacageTwinPanelInfo rightInfo ) {
36 super(user, validator, leftInfo, rightInfo);
60 37
61 protected String dataName; 38 this.leftInfo = leftInfo;
62 39 this.rightInfo = rightInfo;
63 protected User user; 40 }
64 41
65 /** ListGrid that displays user-selected pairs to build differences with. */
66 protected ListGrid differencesList;
67
68 /**
69 * List to track previously selected but now removed pairs. (Needed to
70 * be able to identify artifacts that can be removed from the collection.
71 */
72 protected List<RecommendationPairRecord> removedPairs =
73 new ArrayList<RecommendationPairRecord>();
74
75 /** Service handle to clone and add artifacts to collection. */
76 LoadArtifactServiceAsync loadArtifactService = GWT.create(
77 org.dive4elements.river.client.client.services.LoadArtifactService.class);
78
79 /** Service to remove artifacts from collection. */
80 RemoveArtifactServiceAsync removeArtifactService = GWT.create(
81 org.dive4elements.river.client.client.services.RemoveArtifactService.class);
82
83
84 public DatacageTwinPanel(User user) {
85 super();
86 this.user = user;
87 }
88
89
90 /**
91 * Remove first occurrence of "[" and "]" (if both do occur).
92 * @param value String to be stripped of [] (might be null).
93 * @return input string but with [ and ] removed, or input string if no
94 * brackets were found.
95 * @see StringUtil.unbracket
96 */
97 public static final String unbracket(String value) {
98 // null- guard.
99 if (value == null) return value;
100
101 int start = value.indexOf("[");
102 int end = value.indexOf("]");
103
104 if (start < 0 || end < 0) {
105 return value;
106 }
107
108 value = value.substring(start + 1, end);
109
110 return value;
111 }
112
113
114 /**
115 * Create a recommendation from a string representation of it.
116 * @param from string in format as shown above.
117 * @return recommendation from input string.
118 */
119 public Recommendation createRecommendationFromString(String from, String factory) {
120 // TODO Construct "real" filter.
121 String[] parts = unbracket(from).split(";");
122 Recommendation.Filter filter = new Recommendation.Filter();
123 Recommendation.Facet facet = new Recommendation.Facet(
124 parts[1],
125 parts[2]);
126
127 List<Recommendation.Facet> facets = new ArrayList<Recommendation.Facet>
128 ();
129 facets.add(facet);
130 filter.add("longitudinal_section", facets);
131 Recommendation r = new Recommendation(factory, parts[0],
132 this.artifact.getUuid(), filter);
133 r.setDisplayName(parts[3]);
134 return r;
135 }
136
137
138 /**
139 * Add RecomendationPairRecords from input String to the ListGrid.
140 */
141 public void populateGridFromString(String from, String factory){
142 // Split this string.
143 // Create according recommendations and display strings.
144 String[] recs = from.split("#");
145 if (recs.length % 2 != 0) return;
146 for (int i = 0; i < recs.length; i+=2) {
147 Recommendation minuend =
148 createRecommendationFromString(recs[i+0], factory);
149 Recommendation subtrahend =
150 createRecommendationFromString(recs[i+1], factory);
151
152 RecommendationPairRecord pr = new RecommendationPairRecord(
153 minuend, subtrahend);
154 // This Recommendation Pair comes from the data string and was thus
155 // already cloned.
156 pr.setIsAlreadyLoaded(true);
157 this.differencesList.addData(pr);
158 }
159 }
160
161
162 /**
163 * Creates the graphical representation and interaction widgets for the data.
164 * @param dataList the data.
165 * @return graphical representation and interaction widgets for data.
166 */
167 @Override 42 @Override
168 public Canvas create(DataList dataList) { 43 protected final Canvas createChooserWidgets(final Canvas widget, final DataList dataList, final User user, final ListGrid differencesList) {
169 GWT.log("createData()"); 44 GWT.log("createData()");
170 45
171 Canvas widget = createWidget();
172 Canvas submit = getNextButton(); 46 Canvas submit = getNextButton();
173 47
174 VLayout layout = new VLayout(); 48 VLayout layout = new VLayout();
175 HLayout helperLayout = new HLayout(); 49 HLayout helperLayout = new HLayout();
176 helperLayout.addMember(new DatacagePairWidget(this.artifact, 50
177 user, "winfo_diff_twin_panel", differencesList)); 51 final String leftOuts = leftInfo.getOuts();
52 final String rightOuts = rightInfo.getOuts();
53
54 helperLayout.addMember(new DatacagePairWidget(this.artifact, user, leftOuts, rightOuts, differencesList));
178 55
179 layout.addMember(widget); 56 layout.addMember(widget);
180 layout.addMember(submit); 57 layout.addMember(submit);
181 layout.setMembersMargin(10); 58 layout.setMembersMargin(10);
182 this.helperContainer.addMember(helperLayout); 59 this.helperContainer.addMember(helperLayout);
183 60
184 populateGrid(dataList, "waterlevel");
185 return layout; 61 return layout;
186 } 62 }
187
188 protected void populateGrid(DataList dataList, String factory) {
189 Data data = dataList.get(0);
190 this.dataName = data.getLabel();
191 for (int i = 0; i < dataList.size(); i++) {
192 if (dataList.get(i) != null && dataList.get(i).getItems() != null) {
193 if (dataList.get(i).getItems() != null) {
194 populateGridFromString(
195 dataList.get(i).getItems()[0].getStringValue(),
196 factory);
197 }
198 }
199 }
200 }
201
202
203 /**
204 * Validates the selection.
205 * @return List of internationalized errror messages (if any).
206 */
207 @Override
208 public List<String> validate() {
209 List<String> errors = new ArrayList<String>();
210 if (differencesList.getRecords().length == 0) {
211 errors.add(MSG.error_no_waterlevel_pair_selected());
212 }
213 // Check whether minuend and subtrahend are equal.
214 for (ListGridRecord record: differencesList.getRecords()) {
215 RecommendationPairRecord r = (RecommendationPairRecord) record;
216 if (r.getFirst().equals(r.getSecond())) {
217 errors.add(MSG.error_same_waterlevels_in_pair());
218 }
219 }
220
221 return errors;
222 }
223
224
225 /**
226 * Creates layout with grid that displays selection inside.
227 */
228 public Canvas createWidget() {
229 VLayout layout = new VLayout();
230 differencesList = new ListGrid();
231
232 differencesList.setCanEdit(false);
233 differencesList.setCanSort(false);
234 differencesList.setShowHeaderContextMenu(false);
235 differencesList.setHeight(150);
236 differencesList.setShowAllRecords(true);
237
238 ListGridField nameField = new ListGridField("first", "Minuend");
239 ListGridField capitalField = new ListGridField("second", "Subtrahend");
240 // Track removed rows, therefore more or less reimplement
241 // setCanRecomeRecords.
242 final ListGridField removeField =
243 new ListGridField("_removeRecord", "Remove Record"){{
244 setType(ListGridFieldType.ICON);
245 setIcon(GWT.getHostPageBaseURL() + MSG.removeFeature());
246 setCanEdit(false);
247 setCanFilter(false);
248 setCanSort(false);
249 setCanGroupBy(false);
250 setCanFreeze(false);
251 setWidth(25);
252 }};
253
254 differencesList.setFields(new ListGridField[] {nameField,
255 capitalField, removeField});
256
257 differencesList.addRecordClickHandler(new RecordClickHandler() {
258 @Override
259 public void onRecordClick(final RecordClickEvent event) {
260 // Just handle remove-clicks
261 if(!event.getField().getName().equals(removeField.getName())) {
262 return;
263 }
264 trackRemoved(event.getRecord());
265 event.getViewer().removeData(event.getRecord());
266 }
267 });
268 layout.addMember(differencesList);
269
270 return layout;
271 }
272
273
274 /**
275 * Add record to list of removed records.
276 */
277 public void trackRemoved(Record r) {
278 RecommendationPairRecord pr = (RecommendationPairRecord) r;
279 this.removedPairs.add(pr);
280 }
281
282 /**
283 * Set factory of recommendation such that the correct artifacts will
284 * be cloned for difference calculations.
285 */
286 public void adjustRecommendation(Recommendation recommendation) {
287 // XXX: THIS IS AN EVIL HACK TO MAKE W-DIFFERENCES WORK AGAIN!
288 // TODO: Throw all this code away and do it with server side recommendations!
289 recommendation.setTargetOut("w_differences");
290
291 if (recommendation.getIDs() != null) {
292 GWT.log("Setting staticwkms factory for rec with ID "
293 + recommendation.getIDs());
294 recommendation.setFactory("staticwkms");
295 }
296 /*
297 // So far, we do not need to rewrite the factory anymore,
298 // except for staticwkms; probably other cases will pop up later.
299 else if (recommendation.getFactory().equals("winfo")) {
300 GWT.log("Setting waterlevel factory for a winfo rec.");
301 recommendation.setFactory("waterlevel");
302 }
303 */
304 else {
305 GWT.log("Leave rec. id " + recommendation.getIDs() + ", factory "
306 + recommendation.getFactory() + " untouched.");
307 }
308 }
309
310 /**
311 * Validates data, does nothing if invalid, otherwise clones new selected
312 * waterlevels and add them to collection, forward the artifact.
313 */
314 @Override
315 public void onClick(ClickEvent e) {
316 GWT.log("DatacageTwinPanel.onClick");
317
318 List<String> errors = validate();
319 if (errors != null && !errors.isEmpty()) {
320 showErrors(errors);
321 return;
322 }
323
324 Config config = Config.getInstance();
325 String locale = config.getLocale();
326
327 ListGridRecord[] records = differencesList.getRecords();
328
329 List<Recommendation> ar = new ArrayList<Recommendation>();
330 List<Recommendation> all = new ArrayList<Recommendation>();
331
332 for (ListGridRecord record : records) {
333 RecommendationPairRecord r =
334 (RecommendationPairRecord) record;
335 // Do not add "old" recommendations.
336 if (!r.isAlreadyLoaded()) {
337 // Check whether one of those is a dike or similar.
338 // TODO differentiate and merge: new clones, new, old.
339 Recommendation firstR = r.getFirst();
340 adjustRecommendation(firstR);
341
342 Recommendation secondR = r.getSecond();
343 adjustRecommendation(secondR);
344 ar.add(firstR);
345 ar.add(secondR);
346 }
347 else {
348 all.add(r.getFirst());
349 all.add(r.getSecond());
350 }
351 }
352
353 final Recommendation[] toClone = ar.toArray(new Recommendation[ar.size()]);
354 final Recommendation[] toUse = all.toArray(new Recommendation[all.size()]);
355
356 // Find out whether "old" artifacts have to be removed.
357 List<String> artifactIdsToRemove = new ArrayList<String>();
358 for (RecommendationPairRecord rp: this.removedPairs) {
359 Recommendation first = rp.getFirst();
360 Recommendation second = rp.getSecond();
361
362 for (Recommendation recommendation: toUse) {
363 if (first != null && first.getIDs().equals(recommendation.getIDs())) {
364 first = null;
365 }
366 if (second != null && second.getIDs().equals(recommendation.getIDs())) {
367 second = null;
368 }
369
370 if (first == null && second == null) {
371 break;
372 }
373 }
374 if (first != null) {
375 artifactIdsToRemove.add(first.getIDs());
376 }
377 if (second != null) {
378 artifactIdsToRemove.add(second.getIDs());
379 }
380 }
381
382 // Remove old artifacts, if any. Do this asychronously without much
383 // feedback.
384 for(final String uuid: artifactIdsToRemove) {
385 removeArtifactService.remove(this.collection,
386 uuid,
387 locale,
388 new AsyncCallback<Collection>() {
389 @Override
390 public void onFailure(Throwable caught) {
391 GWT.log("RemoveArtifact (" + uuid + ") failed.");
392 }
393 @Override
394 public void onSuccess(Collection collection) {
395 GWT.log("RemoveArtifact succeeded");
396 }
397 });
398 }
399
400 // Clone new ones (and spawn statics), go forward.
401 parameterList.lockUI();
402 loadArtifactService.loadMany(
403 this.collection,
404 toClone,
405 //"staticwkms" and "waterlevel"
406 null,
407 locale,
408 new AsyncCallback<Artifact[]>() {
409 @Override
410 public void onFailure(Throwable caught) {
411 GWT.log("Failure of cloning with factories!");
412 parameterList.unlockUI();
413 }
414 @Override
415 public void onSuccess(Artifact[] artifacts) {
416 GWT.log("Successfully cloned " + toClone.length +
417 " with factories.");
418
419 fireStepForwardEvent(new StepForwardEvent(
420 getData(toClone, artifacts, toUse)));
421 parameterList.unlockUI();
422 }
423 });
424 }
425
426
427 /**
428 * Create Data and DataItem from selection (a long string with identifiers
429 * to construct diff-pairs).
430 *
431 * @param newRecommendations "new" recommendations (did not survive a
432 * backjump).
433 * @param newArtifacts artifacts cloned from newRecommendations.
434 * @param oldRecommendations old recommendations that survived a backjump.
435 *
436 * @return dataitem with a long string with identifiers to construct
437 * diff-pairs.
438 */
439 protected Data[] getData(
440 Recommendation[] newRecommendations,
441 Artifact[] newArtifacts,
442 Recommendation[] oldRecommendations)
443 {
444 // Construct string with info about selections.
445 String dataItemString = "";
446 for (int i = 0; i < newRecommendations.length; i++) {
447 Recommendation r = newRecommendations[i];
448 Artifact newArtifact = newArtifacts[i];
449 String uuid = newArtifact.getUuid();
450 r.setMasterArtifact(uuid);
451 if (i>0) dataItemString += "#";
452
453 dataItemString += createDataString(uuid, r);
454 }
455
456 for (int i = 0; i < oldRecommendations.length; i++) {
457 Recommendation r = oldRecommendations[i];
458 String uuid = r.getIDs();
459 if (dataItemString.length() > 0) dataItemString += "#";
460
461 dataItemString += createDataString(uuid, r);
462 }
463
464 // TODO some hassle could be resolved by using multiple DataItems
465 // (e.g. one per pair).
466 DataItem item = new DefaultDataItem(dataName, dataName, dataItemString);
467 return new Data[] { new DefaultData(
468 dataName, null, null, new DataItem[] {item}) };
469 }
470
471
472 protected String createDataString(String artifact, Recommendation recommendation) {
473 return createDataString(artifact, recommendation, "staticwkms");
474 }
475
476 /**
477 * Creates part of the String that encodes minuend or subtrahend.
478 * @param artifact Artifacts UUID.
479 * @param recommendation Recommendation to wrap in string.
480 * @param factory The factory to encode.
481 */
482 protected String createDataString(
483 String artifact,
484 Recommendation recommendation,
485 String factory)
486 {
487 Filter filter = recommendation.getFilter();
488 Facet f = null;
489
490 if(filter != null) {
491 Map<String, List<Facet>> outs = filter.getOuts();
492 Set<Map.Entry<String, List<Facet>>> entries = outs.entrySet();
493
494 for (Map.Entry<String, List<Facet>> entry: entries) {
495 List<Facet> fs = entry.getValue();
496
497 f = fs.get(0);
498 if (f != null) {
499 break;
500 }
501 }
502
503 return "[" + artifact + ";"
504 + f.getName()
505 + ";"
506 + f.getIndex()
507 + ";"
508 + recommendation.getDisplayName() + "]";
509 }
510 else {
511 return "["
512 + artifact
513 + ";" + factory + ";0;"
514 + recommendation.getDisplayName() + "]";
515 }
516 }
517 } 63 }
518 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org