Mercurial > dive4elements > river
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 : |