comparison gwt-client/src/main/java/org/dive4elements/river/client/client/ui/chart/CrossSectionChartThemePanel.java @ 5838:5aa05a7a34b7

Rename modules to more fitting names.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 15:23:37 +0200
parents flys-client/src/main/java/org/dive4elements/river/client/client/ui/chart/CrossSectionChartThemePanel.java@821a02bbfb4e
children 172338b1407f
comparison
equal deleted inserted replaced
5837:d9901a08d0a6 5838:5aa05a7a34b7
1 package org.dive4elements.river.client.client.ui.chart;
2
3 import com.google.gwt.core.client.GWT;
4 import com.google.gwt.user.client.rpc.AsyncCallback;
5
6 import com.smartgwt.client.types.ListGridFieldType;
7 import com.smartgwt.client.util.SC;
8 import com.smartgwt.client.widgets.Canvas;
9 import com.smartgwt.client.widgets.form.DynamicForm;
10 import com.smartgwt.client.widgets.form.fields.SelectItem;
11 import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
12 import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;
13 import com.smartgwt.client.widgets.grid.ListGrid;
14 import com.smartgwt.client.widgets.grid.ListGridField;
15 import com.smartgwt.client.widgets.grid.ListGridRecord;
16 import com.smartgwt.client.widgets.layout.VLayout;
17 import com.smartgwt.client.widgets.menu.Menu;
18 import com.smartgwt.client.widgets.menu.MenuItem;
19 import com.smartgwt.client.widgets.menu.events.ClickHandler;
20 import com.smartgwt.client.widgets.menu.events.MenuItemClickEvent;
21
22 import org.dive4elements.river.client.client.Config;
23 import org.dive4elements.river.client.client.services.CrossSectionKMServiceAsync;
24 import org.dive4elements.river.client.client.services.LoadArtifactService;
25 import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync;
26 import org.dive4elements.river.client.client.ui.CollectionView;
27 import org.dive4elements.river.client.client.widgets.KMSpinner;
28 import org.dive4elements.river.client.client.widgets.KMSpinnerChangeListener;
29 import org.dive4elements.river.client.shared.model.Artifact;
30 import org.dive4elements.river.client.shared.model.Data;
31 import org.dive4elements.river.client.shared.model.DefaultArtifact;
32 import org.dive4elements.river.client.shared.model.DefaultData;
33 import org.dive4elements.river.client.shared.model.FacetRecord;
34 import org.dive4elements.river.client.shared.model.OutputMode;
35 import org.dive4elements.river.client.shared.model.Theme;
36 import org.dive4elements.river.client.shared.model.ThemeList;
37
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44
45
46 /**
47 * ThemePanel much like ChartThemePanel, but shows an "Actions" column,
48 * needed for interaction in the CrossSection Charts and a selector to
49 * declare which cross section profile is "master" (waterlevels refer to the
50 * chosen kilometer of that cross section profile).
51 * Also can show 'area creation' context menus.
52 */
53 public class CrossSectionChartThemePanel
54 extends ChartThemePanel
55 implements KMSpinnerChangeListener
56 {
57 /** Artifact Clone/Creation service. */
58 protected LoadArtifactServiceAsync loadService =
59 GWT.create(LoadArtifactService.class);
60
61 /** Service to query measurement points of cross sections. */
62 CrossSectionKMServiceAsync kmService = GWT.create(
63 org.dive4elements.river.client.client.services.CrossSectionKMService.class);
64
65 /** UUID of the current "master" cross section. */
66 protected String currentCSMasterUUID;
67
68 /** The layout (used for visual active/inactive feedback). */
69 protected VLayout layout;
70
71 /** Data item name for CrossSections selected km. */
72 protected static String CS_KM = "cross_section.km";
73
74 /** Data item name for CrossSections selected km. */
75 protected static String CS_IS_MASTER = "cross_section.master?";
76
77 /** List of cross-section themes through which is moved through synchronously. */
78 protected HashSet synchronCrossSectionThemes = new HashSet();
79
80 /** Data for master artifact combobox.*/
81 protected LinkedHashMap<String, String> masters;
82
83 /** Combobox for master artifacts.*/
84 protected SelectItem masterCb;
85
86
87 /**
88 * Trivial constructor.
89 */
90 public CrossSectionChartThemePanel(
91 OutputMode mode,
92 CollectionView view)
93 {
94 super(mode, view);
95 }
96
97
98 /** Create DefaultArtifact. */
99 public static DefaultArtifact artifactReference(String uuid) {
100 return new DefaultArtifact(uuid, "TODO:hash");
101 }
102
103
104 /** Access data of collection item of theme. */
105 public static String dataOf(Theme theme, String dataItemName) {
106 if (theme != null && theme.getCollectionItem() != null
107 && theme.getCollectionItem().getData() != null
108 ) {
109 return theme.getCollectionItem().getData().get(dataItemName);
110 }
111
112 return null;
113 }
114
115
116 /**
117 * Feed an artifact to let it know that it is master wrt cross-sections.
118 * @param artifact uuid of an artifact.
119 */
120 public void feedTellMaster(final String artifact) {
121 Data[] feedData = DefaultData.createSimpleStringDataArray(
122 CS_IS_MASTER, "1");
123
124 feedService.feed(
125 Config.getInstance().getLocale(),
126 artifactReference(artifact),
127 feedData,
128 new AsyncCallback<Artifact>() {
129 @Override
130 public void onFailure(Throwable caught) {
131 GWT.log("Could not feed artifact (" + artifact
132 + ") with master marker: " + caught.getMessage());
133 SC.warn(MSG.getString(caught.getMessage()));
134 enable();
135 }
136 @Override
137 public void onSuccess(Artifact artifact) {
138 GWT.log("Successfully injected master mark to " + artifact);
139 setCurrentCSMaster(artifact.getUuid());
140 requestRedraw();
141 enable();
142 }
143 });
144 }
145
146
147 /**
148 * Sets currentCSMasterUUID.
149 */
150 public String findCurrentCSMaster() {
151 ThemeList themeList = getThemeList();
152 int count = getThemeList().getThemeCount();
153 String firstCSUuid = null;
154
155 for (int i = 1; i <= count; i++) {
156 Theme theme = themeList.getThemeAt(i);
157 String value = dataOf(theme, CS_IS_MASTER);
158
159 if (value != null) {
160 if (firstCSUuid == null) {
161 firstCSUuid = theme.getArtifact();
162 }
163 if (!value.equals("0")) {
164 setCurrentCSMaster(theme.getArtifact());
165 GWT.log("found a master: " + currentCSMasterUUID
166 + "/" + theme.getDescription());
167 return theme.getDescription();
168 }
169 }
170 }
171 // There is none selected. Take the first one.
172 if (firstCSUuid != null) {
173 setCurrentCSMaster(firstCSUuid);
174 feedTellMaster(firstCSUuid);
175 }
176 return null;
177 }
178
179
180 /**
181 * Create Layout, add a master selection box beneath.
182 */
183 @Override
184 protected VLayout createLayout() {
185 layout = super.createLayout();
186
187 // Create "set master" combobox.
188 masterCb = new SelectItem();
189
190 masterCb.setTitle(MSG.chart_themepanel_set_master());
191 masterCb.setType("comboBox");
192 masters = getThemeList().toMapArtifactUUIDDescription("cross_section");
193 masterCb.setValueMap(masters);
194
195 final DynamicForm form = new DynamicForm();
196 form.setWidth(200);
197 form.setFields(masterCb);
198 layout.addMember(form, 0);
199
200 Config config = Config.getInstance();
201 final String locale = config.getLocale();
202 findCurrentCSMaster();
203 masterCb.setValue(getCurrentCSMaster());
204
205 // Add Change Handler to first unset the old master and then set the
206 // new master.
207 masterCb.addChangeHandler(new ChangeHandler() {
208 @Override
209 public void onChange(ChangeEvent event) {
210 String selectedItem = (String) event.getValue();
211 final String artifact = selectedItem;
212
213 disable();
214
215 // Tell current master that he is not master anymore.
216 if (getCurrentCSMaster() != null) {
217 Data[] feedData = DefaultData.createSimpleStringDataArray(
218 CS_IS_MASTER, "0");
219 feedService.feed(
220 locale,
221 artifactReference(getCurrentCSMaster()),
222 feedData,
223 new AsyncCallback<Artifact>() {
224 @Override
225 public void onFailure(Throwable caught) {
226 GWT.log("Could not un-master artifact ("
227 + getCurrentCSMaster() + "): " +
228 caught.getMessage());
229 SC.warn(MSG.getString(caught.getMessage()));
230 enable();
231 }
232 @Override
233 public void onSuccess(Artifact oldMaster) {
234 GWT.log("Successfully un-mastered artifact.");
235 feedTellMaster(artifact);
236 }
237 });
238 }
239 else {
240 feedTellMaster(artifact);
241 }
242 }
243 });
244
245 return layout;
246 }
247
248
249 /** Disable the UI (becomes gray, inresponsive to user input). */
250 @Override
251 public void disable() {
252 this.layout.setDisabled(true);
253 }
254
255
256 /** DisDisable the UI (becomes ungray, responsive to user input). */
257 @Override
258 public void enable() {
259 this.layout.setDisabled(false);
260 }
261
262
263 /**
264 * Returns a double from the list that has the smallest distance to the
265 * given to value. In case of multiple values with the same difference,
266 * the last one is taken.
267 * @param in possible return values.
268 * @param to the value to be as close to as possible.
269 * @param up if true, prefer numerically higher values in case of two
270 * values with equal distance to \param to.
271 * @return value from in that is closest to to, -1 if none.
272 */
273 public static double closest(Double[] in, double to, boolean up) {
274 if (in == null || in.length == 0) {
275 return -1;
276 }
277 if (in[0] == to) {
278 return to;
279 }
280 for (int i = 0; i < in.length; i++) {
281 GWT.log ("Close? " + in[i]);
282 }
283
284 double minDiff = Math.abs(to - in[0]);
285 double bestMatch = in[0];
286 for (int i = 1; i < in.length; i++) {
287 if (in[i] == to) {
288 return to;
289 }
290 if ((in[i] < to && up)
291 || (in[i] > to && !up)) {
292 continue;
293 }
294 double diff = Math.abs(to - in[i]);
295 if (diff <= minDiff) {
296 minDiff = diff;
297 bestMatch = in[i];
298 }
299 }
300 return bestMatch;
301 }
302
303
304 /**
305 * Feed a single artifact with the km of the crosssection to display.
306 * If its the selected master, also feed the collectionmaster.
307 *
308 * @param artifacts List of artifacts to feed.
309 * @param kmD The km to set.
310 */
311 public void sendFeed(final List<Artifact> artifacts, final double kmD) {
312 Config config = Config.getInstance();
313 final String locale = config.getLocale();
314
315 Data[] feedData =
316 DefaultData.createSimpleStringDataArray(CS_KM,
317 Double.valueOf(kmD).toString());
318
319 disable();
320 // TODO
321 // The ones who do not have data for this km should not show line!
322 feedService.feedMany(
323 locale,
324 artifacts,
325 feedData,
326 new AsyncCallback<List<Artifact>>() {
327 @Override
328 public void onFailure(Throwable caught) {
329 GWT.log("Could not feed many artifacts " + caught.getMessage());
330 SC.warn(MSG.getString(caught.getMessage()));
331 enable();
332 }
333 @Override
334 public void onSuccess(List<Artifact> artifact) {
335 GWT.log("Successfully fed many with km");
336 requestRedraw();
337 enable();
338 }
339 });
340 }
341
342
343 /**
344 * Feed a single artifact with the km of the crosssection to display.
345 * If its the selected master, also feed the collectionmaster.
346 * @param artUUID The UUID of the artifact to feed.
347 * @param kmD The km to set.
348 */
349 public void sendFeed(final String artUUID, final double kmD) {
350 Config config = Config.getInstance();
351 final String locale = config.getLocale();
352
353 Data[] feedData =
354 DefaultData.createSimpleStringDataArray(CS_KM,
355 Double.valueOf(kmD).toString());
356
357 disable();
358 feedService.feed(
359 locale,
360 artifactReference(artUUID),
361 feedData,
362 new AsyncCallback<Artifact>() {
363 @Override
364 public void onFailure(Throwable caught) {
365 GWT.log("Could not feed artifact " + caught.getMessage());
366 SC.warn(MSG.getString(caught.getMessage()));
367 enable();
368 }
369 @Override
370 public void onSuccess(Artifact artifact) {
371 GWT.log("Successfully fed with km");
372 requestRedraw();
373 enable();
374 }
375 });
376 }
377
378
379 /** Remove the themes, also from the master (reference) select box. */
380 @Override
381 protected void removeThemes(final ListGridRecord[] records) {
382 // TODO take care of what happens if that was the last
383 // cross section and/or the cross section currently selected as master.
384 for (ListGridRecord record: records) {
385 FacetRecord facet = (FacetRecord) record;
386 Theme theme = facet.getTheme();
387 masters.remove(theme.getArtifact());
388 }
389 masterCb.setValueMap(masters);
390 super.removeThemes(records);
391 }
392
393
394 /**
395 * Callback for when a value has been accepted in the km-spinner
396 * of a Cross-Section Profile theme.
397 * @param item The SpinnerItem which was manipulated.
398 * @param enteredKm The double-parsed value that has been entered.
399 * @param facetRecord The underlying datastores record.
400 * @param up If true, numerically higher values are preferred if
401 * two values in \param in are in the same distance to
402 * \param to.
403 */
404 @Override
405 public void spinnerValueEntered(KMSpinner spinner, final double enteredKm, final FacetRecord facetRecord, final boolean up) {
406 disable();
407 Config config = Config.getInstance();
408 final String locale = config.getLocale();
409
410 Map<Integer, Double> map = new HashMap<Integer,Double>();
411 int _dbid = -1;
412 try {
413 _dbid = Integer.valueOf(facetRecord.getTheme()
414 .getCollectionItem()
415 .getData().get("cross_section.dbid"));
416 }
417 catch (NumberFormatException nfe) {
418 GWT.log("Could not extract cross-section db id from data.");
419 }
420 final int dbid = _dbid;
421
422 map.put(dbid, enteredKm);
423
424 // Query the available cross section measurements.
425 kmService.getCrossSectionKMs(locale, map, 2,
426 new AsyncCallback<Map<Integer, Double[]>>() {
427 @Override
428 public void onFailure(Throwable caught) {
429 GWT.log("Could not get single km for "
430 + dbid + ": "+ caught.getMessage());
431 SC.warn(MSG.getString(caught.getMessage()));
432 updateCollection();
433 //updateGrid();
434 enable();
435 }
436
437 @Override
438 public void onSuccess(Map<Integer, Double[]> obj) {
439 Double[] kms = obj.get(dbid);
440 double closest =
441 CrossSectionChartThemePanel.closest(kms, enteredKm, up);
442 GWT.log("Got single km close to " + enteredKm + " for " + dbid + ", it is "
443 + closest);
444
445 // Do not set value, as it will trigger strange
446 // "javascript" bugs. /*item.setValue(closest);*/
447 if (synchronCrossSectionThemes.contains (themeHash
448 (facetRecord.getTheme()))) {
449 // Move all other synchrons
450 ThemeList themes = getThemeList();
451 int nThemes = themes.getThemeCount();
452 List<Artifact> artifacts = new ArrayList<Artifact>();
453 for (int i = 0; i < nThemes; i++) {
454 final Theme theme = themes.getThemeAt(i+1);
455 if (theme.getFacet().equals("cross_section") &&
456 theme.getActive() == 1 &&
457 synchronCrossSectionThemes.contains(themeHash(theme))
458 ) {
459 artifacts.add(artifactReference(theme.getArtifact()));
460 }
461 }
462 sendFeed(artifacts, closest);
463 }
464 else {
465 sendFeed(facetRecord.getTheme().getArtifact(),
466 closest);
467 }
468 }
469 });
470 }
471
472
473 /**
474 * Create and configure the Grid to display.
475 * @return ListGrid with Themes and related controls inside.
476 */
477 @Override
478 protected ListGrid createGrid() {
479 final CrossSectionChartThemePanel parent = this;
480
481 ListGrid list = new ListGrid() {
482 @Override
483 protected Canvas createRecordComponent(
484 final ListGridRecord record,
485 Integer colNum)
486 {
487 // Only cross_section Facets display an action widget.
488 final FacetRecord facetRecord = (FacetRecord) record;
489 if (!facetRecord.getTheme().getFacet().equals(
490 "cross_section"))
491 {
492 return null;
493 }
494
495 String fieldName = this.getFieldName(colNum);
496
497 if (fieldName.equals(GRID_FIELD_ACTIONS)) {
498 double currentValue =
499 Double.valueOf(facetRecord.getTheme().getCollectionItem().getData().get(CS_KM));
500 KMSpinner kmSpinner = new KMSpinner(currentValue, facetRecord);
501 kmSpinner.addChangeListener(parent);
502 return kmSpinner;
503 }
504 else {
505 return null;
506 }
507 }
508 };
509 list.setCanResizeFields(true);
510 list.setShowRecordComponents(true);
511 list.setShowRecordComponentsByCell(true);
512 list.setShowAllRecords(true);
513 list.setShowHeaderContextMenu(false);
514 list.setLeaveScrollbarGap(false);
515 return list;
516 }
517
518
519 /**
520 * Initializes the components (columns) of the theme grid.
521 */
522 @Override
523 protected void initGrid() {
524 list.setCanEdit(true);
525 list.setCanSort(false);
526 list.setShowRecordComponents(true);
527 list.setShowRecordComponentsByCell(true);
528 list.setShowHeader(true);
529 list.setWidth100();
530 list.setHeight100();
531
532 list.addEditCompleteHandler(this);
533
534 ListGridField active = new ListGridField(GRID_FIELD_ACTIVE, " ", 20);
535 active.setType(ListGridFieldType.BOOLEAN);
536
537 ListGridField name = new ListGridField(
538 GRID_FIELD_NAME, MSG.chart_themepanel_header_themes());
539 name.setType(ListGridFieldType.TEXT);
540
541 ListGridField actions = new ListGridField(GRID_FIELD_ACTIONS,
542 MSG.chart_themepanel_header_actions(), 100);
543
544 list.setFields(active, name, actions);
545 }
546
547
548 /** Get Current Cross-section Masters uuid. */
549 public String getCurrentCSMaster() {
550 return currentCSMasterUUID;
551 }
552
553
554 /** Set Current Cross-section Masters uuid. */
555 public void setCurrentCSMaster(String currentMasterUuid) {
556 this.currentCSMasterUUID = currentMasterUuid;
557 }
558
559
560 /** Returns name of cross section area facets. */
561 @Override
562 protected String getAreaFacetName() {
563 return "cross_section.area";
564 }
565
566
567 /**
568 * Return true if two themes are canditates for an area being
569 * rendered between them.
570 * TODO join with canArea, generalize to allow easier modification
571 * in subclasses.
572 */
573 @Override
574 protected boolean areAreaCompatible(Theme a, Theme b) {
575 if (a.equals(b)) {
576 return false;
577 }
578 return (a.getFacet().equals("cross_section")
579 || a.getFacet().endsWith("line"))
580 && (b.getFacet().equals("cross_section")
581 || b.getFacet().endsWith("line"));
582 }
583
584
585 /**
586 * True if context menu should contain 'create area' submenu on
587 * this theme.
588 */
589 @Override
590 protected boolean canArea(Theme a) {
591 return a.getFacet().equals("cross_section")
592 || a.getFacet().equals("cross_section_water_line")
593 || a.getFacet().endsWith("line");
594 }
595
596
597 protected String themeHash(Theme theme) {
598 return theme.getArtifact() + theme.getFacet() + theme.getIndex();
599 }
600
601
602 /**
603 * Include synchron navigation item.
604 */
605 @Override
606 protected Menu getSingleContextMenu(final ListGridRecord[] records) {
607 Menu contextMenu = super.getSingleContextMenu(records);
608
609 Theme facetTheme = ((FacetRecord)records[0]).getTheme();
610 String item = facetTheme.getFacet();
611
612 if (item.equals("cross_section")) {
613 // Synchron checking.
614 MenuItem synchronNavigationMenuItem = new MenuItem();
615 final String themeHash = themeHash(facetTheme);
616 if (synchronCrossSectionThemes.contains(themeHash)) {
617 synchronNavigationMenuItem.setTitle(MSG.chart_themepanel_asynchron());
618 synchronNavigationMenuItem.addClickHandler(new ClickHandler() {
619 @Override
620 public void onClick(MenuItemClickEvent evt) {
621 synchronCrossSectionThemes.remove (themeHash);
622 }
623 });
624 }
625 else {
626 synchronNavigationMenuItem.setTitle(MSG.chart_themepanel_synchron());
627 synchronNavigationMenuItem.addClickHandler(new ClickHandler() {
628 @Override
629 public void onClick(MenuItemClickEvent evt) {
630 synchronCrossSectionThemes.add (themeHash);
631 }
632 });
633 }
634 contextMenu.addItem(synchronNavigationMenuItem);
635 }
636
637 return contextMenu;
638 }
639
640
641 /**
642 * This method is used to clear the current theme grid and add new updated
643 * data.
644 */
645 @Override
646 protected void updateGrid() {
647 GWT.log("CrossSectionChartThemePanel.updateGrid");
648
649 ListGridRecord[] selected = list.getSelectedRecords();
650
651 clearGrid();
652
653 ThemeList themeList = getThemeList();
654
655 if (themeList == null) {
656 GWT.log("ERROR: No theme list.");
657 return;
658 }
659
660 int count = themeList.getThemeCount();
661
662 for (int i = 1; i <= count; i++) {
663 Theme theme = themeList.getThemeAt(i);
664
665 if (theme == null) {
666 continue;
667 }
668
669 if (theme.getFacet().equals("empty.facet")) {
670 theme.setVisible(0);
671 }
672
673 if (theme.getVisible() == 0) {
674 continue;
675 }
676
677 if (theme.getFacet().equals("cross_section")) {
678 addToReferences(theme);
679 }
680
681 FacetRecord newRecord = createRecord(theme);
682 addFacetRecord(newRecord);
683
684 String newArtifact = theme.getArtifact();
685 String newFacet = theme.getFacet();
686 int newIndex = theme.getIndex();
687
688 for (ListGridRecord r: selected) {
689 FacetRecord sel = (FacetRecord) r;
690 Theme oldTheme = sel.getTheme();
691
692 if (oldTheme.getArtifact().equals(newArtifact)
693 && oldTheme.getFacet().equals(newFacet)
694 && oldTheme.getIndex() == newIndex) {
695 list.selectRecord(newRecord);
696 }
697 }
698 }
699
700 fireOutputParameterChanged();
701
702 }
703
704
705 /**
706 * Adds a cross section theme to the master artifacts combobox and finds
707 * a new master if necessary.
708 *
709 * @param theme The cross section theme.
710 */
711 protected void addToReferences(Theme theme) {
712 if (theme.getVisible() != 0) {
713 masters.put(theme.getArtifact(), theme.getDescription());
714 masterCb.setValueMap(masters);
715 }
716 findCurrentCSMaster();
717 if (masterCb.getSelectedRecord() == null) {
718 masterCb.setValue(getCurrentCSMaster());
719 }
720 }
721 }
722 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org