# HG changeset patch # User Felix Wolfsteller # Date 1323774708 0 # Node ID 29fc2d1dfe9b426288fcba664f43cb84dfb6f8a3 # Parent f6fbfdc813f09d23fd4133ed73ba9a2cc3db0577 Client side of interactive cross section diagrams. flys-client/trunk@3399 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r f6fbfdc813f0 -r 29fc2d1dfe9b flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.java --- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.java Tue Dec 13 09:51:47 2011 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.java Tue Dec 13 11:11:48 2011 +0000 @@ -378,6 +378,8 @@ String chart_themepanel_header_actions(); + String chart_themepanel_set_master(); + String discharge(); // ERRORS diff -r f6fbfdc813f0 -r 29fc2d1dfe9b flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.properties --- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.properties Tue Dec 13 09:51:47 2011 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.properties Tue Dec 13 11:11:48 2011 +0000 @@ -185,6 +185,7 @@ computed_dischargecurve_at_export = AT Export chart_themepanel_header_themes = Theme chart_themepanel_header_actions = Actions +chart_themepanel_set_master = Reference ele_window_title = Elevation Node ele_window_label = Elevation settings in m \u00fc. NN. diff -r f6fbfdc813f0 -r 29fc2d1dfe9b flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_de.properties --- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_de.properties Tue Dec 13 09:51:47 2011 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_de.properties Tue Dec 13 11:11:48 2011 +0000 @@ -185,6 +185,7 @@ computed_dischargecurve_at_export = AT Export chart_themepanel_header_themes = Thema chart_themepanel_header_action = Aktionen +chart_themepanel_set_master = Referenz ele_window_title = H\u00f6henknoten setzen ele_window_label = H\u00f6henangaben in m \u00fc. NN. diff -r f6fbfdc813f0 -r 29fc2d1dfe9b flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_en.properties --- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_en.properties Tue Dec 13 09:51:47 2011 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_en.properties Tue Dec 13 11:11:48 2011 +0000 @@ -185,6 +185,7 @@ computed_dischargecurve_at_export = AT Export chart_themepanel_header_themes = Theme chart_themepanel_header_actions = Actions +chart_themepanel_set_master = Reference ele_window_title = Elevation Node ele_window_label = Elevation settings in m \u00fc. NN. diff -r f6fbfdc813f0 -r 29fc2d1dfe9b flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java --- a/flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java Tue Dec 13 09:51:47 2011 +0000 +++ b/flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java Tue Dec 13 11:11:48 2011 +0000 @@ -1,54 +1,251 @@ package de.intevation.flys.client.client.ui.chart; +import java.util.HashMap; +import java.util.Map; +import java.util.LinkedHashMap; + import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import com.smartgwt.client.util.SC; + import com.smartgwt.client.types.Alignment; +import com.smartgwt.client.types.ListGridFieldType; + import com.smartgwt.client.widgets.Canvas; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.smartgwt.client.types.ListGridFieldType; 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.layout.HLayout; +import com.smartgwt.client.widgets.layout.VLayout; import com.smartgwt.client.widgets.form.fields.SpinnerItem; import com.smartgwt.client.widgets.form.DynamicForm; + +import com.smartgwt.client.widgets.form.fields.ComboBoxItem; +import com.smartgwt.client.widgets.form.fields.SelectItem; +import com.smartgwt.client.widgets.form.fields.events.ChangeEvent; import com.smartgwt.client.widgets.form.fields.events.ChangedEvent; import com.smartgwt.client.widgets.form.fields.events.ChangedHandler; +import com.smartgwt.client.widgets.form.fields.events.ChangeHandler; import de.intevation.flys.client.client.Config; import de.intevation.flys.client.shared.model.Artifact; import de.intevation.flys.client.shared.model.Collection; -import de.intevation.flys.client.shared.model.OutputMode; - +import de.intevation.flys.client.shared.model.CollectionItem; import de.intevation.flys.client.shared.model.Data; import de.intevation.flys.client.shared.model.DataItem; import de.intevation.flys.client.shared.model.DefaultArtifact; import de.intevation.flys.client.shared.model.DefaultData; import de.intevation.flys.client.shared.model.DefaultDataItem; import de.intevation.flys.client.shared.model.FacetRecord; +import de.intevation.flys.client.shared.model.OutputMode; +import de.intevation.flys.client.shared.model.Theme; +import de.intevation.flys.client.shared.model.ThemeList; import de.intevation.flys.client.client.services.FeedServiceAsync; - +import de.intevation.flys.client.client.services.CrossSectionKMServiceAsync; /** * ThemePanel much like ChartThemePanel, but shows an "Actions" column, - * needed for interaction in the CrossSection Charts. + * needed for interaction in the CrossSection Charts and a selctor to + * declare which cross section profile is "master". */ public class CrossSectionChartThemePanel extends ChartThemePanel { + /** Service to query measurement points of cross sections. */ + CrossSectionKMServiceAsync kmService = GWT.create( + de.intevation.flys.client.client.services.CrossSectionKMService.class); + + /** UUID of the current "master" cross section. */ + protected String currentMasterUUID; + + /** The layout (used for visual active/inactive feedback). */ + protected VLayout layout; + /** * Trivial constructor. */ public CrossSectionChartThemePanel( - Collection collection, - OutputMode mode) + Collection collection, + OutputMode mode) { super(collection, mode); } /** + * Feed an artifact to let it know that it is master wrt cross-sections. + * @param artifact uuid of an artifact. + */ + public void feedTellMaster(final String artifact) { + Data[] feedData = DefaultData.createSimpleStringDataArray( + "cross_section.master?", "1"); + + feedService.feed( + Config.getInstance().getLocale(), + new DefaultArtifact(artifact, "TODO:hash"), + feedData, + new AsyncCallback() { + public void onFailure(Throwable caught) { + GWT.log("Could not feed artifact (" + artifact + + ") with master marker: " + caught.getMessage()); + SC.warn(MSG.getString(caught.getMessage())); + enable(); + } + public void onSuccess(Artifact artifact) { + GWT.log("Successfully injected master mark to " + artifact); + setCurrentMaster(artifact.getUuid()); + requestRedraw(); + enable(); + } + }); + } + + + /** + * Create Layout, add a master selection box beneath. + */ + @Override + protected VLayout createLayout() { + + layout = super.createLayout(); + + // Create "set master" combobox. + SelectItem cbItem = new SelectItem(); + + cbItem.setTitle(MSG.chart_themepanel_set_master()); + cbItem.setType("comboBox"); + final LinkedHashMap valueMap = + getThemeList().toMapArtifactUUIDDescription("cross_section"); + cbItem.setValueMap(valueMap); + + final DynamicForm form = new DynamicForm(); + form.setWidth(200); + form.setFields(cbItem); + layout.addMember(form); + + Config config = Config.getInstance(); + final String locale = config.getLocale(); + + // Add Change Handler to first unset the old master and then set the + // new master. + cbItem.addChangeHandler(new ChangeHandler() { + public void onChange(ChangeEvent event) { + String selectedItem = (String) event.getValue(); + final String artifact = selectedItem; + + disable(); + + // Tell current master that he is not master anymore. + if (getCurrentMaster() != null) { + Data[] feedData = DefaultData.createSimpleStringDataArray( + "cross_section.master?", "0"); + feedService.feed( + locale, + new DefaultArtifact(getCurrentMaster(), "TODO:hash"), + feedData, + new AsyncCallback() { + public void onFailure(Throwable caught) { + GWT.log("Could not un-master artifact: " + + caught.getMessage()); + SC.warn(MSG.getString(caught.getMessage())); + enable(); + } + public void onSuccess(Artifact oldMaster) { + GWT.log("Successfully un-mastered artifact."); + feedTellMaster(artifact); + } + }); + } + else { + feedTellMaster(artifact); + } + } + }); + + return layout; + } + + + /** Disable the UI (becomes gray, inresponsive to user input). */ + public void disable() { + this.layout.setDisabled(true); + } + + + /** DisDisable the UI (becomes ungray, responsive to user input). */ + public void enable() { + this.layout.setDisabled(false); + } + + + /** + * Returns a double from the list that has the smallest distance to the + * given to value. In case of multiple values with the same difference, + * the last one is taken. + * @param in possible return values. + * @param to the value to be as close to as possible. + * @return value from in that is closest to to, -1 if none. + */ + public static double closest(Double[] in, double to) { + if (in == null || in.length == 0) { + return -1; + } + if (in[0] == to) { + return to; + } + double minDiff = Math.abs(to - in[0]); + double bestMatch = in[0]; + for (int i = 1; i < in.length; i++) { + if (in[i] == to) { + return to; + } + double diff = Math.abs(to - in[0]); + if (diff < minDiff) { + minDiff = diff; + bestMatch = in[i]; + } + } + return bestMatch; + } + + + /** Feed a single artifact with the km of the crosssection to display. */ + public void sendFeed(FacetRecord facetRecord, double kmD) { + Config config = Config.getInstance(); + final String locale = config.getLocale(); + + Data[] feedData = + DefaultData.createSimpleStringDataArray("cross_section.km", + Double.valueOf(kmD).toString()); + + disable(); + feedService.feed( + locale, + new DefaultArtifact( + facetRecord.getTheme().getArtifact(), + "TODO:hash"), + feedData, + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + GWT.log("Could not feed artifact " + caught.getMessage()); + SC.warn(MSG.getString(caught.getMessage())); + enable(); + } + @Override + public void onSuccess(Artifact artifact) { + GWT.log("Successfully fed"); + requestRedraw(); + enable(); + } + }); + } + + + /** * Create the handler for ChangeEvents on the Spinner in the * facets that control km of cross section. * @@ -66,37 +263,55 @@ ChangedHandler handler = new ChangedHandler() { @Override - public void onChanged(ChangedEvent ce) { + public void onChanged(final ChangedEvent ce) { if (ce.getValue() == null) { return; } - DefaultDataItem kmItem = new DefaultDataItem( - "cross_section.km", - "cross_section.km", - ce.getValue().toString()); - DefaultData km = new DefaultData( - "cross_section.km", - null, - null, - new DataItem[] {kmItem}); - Data[] feedData = new Data[] {km}; - feedService.feed( - locale, - new DefaultArtifact( - facetRecord.getTheme().getArtifact(), - "TODO:hash"), - feedData, - new AsyncCallback() { + + // Now, query the value with the kmService. + Map map = new HashMap(); + final double selected_km = + Double.parseDouble(ce.getValue().toString()); + + int _dbid = -1; + try { + _dbid = Integer.valueOf(facetRecord.getTheme() + .getCollectionItem() + .getData().get("cross_section.dbid")); + } + catch (NumberFormatException nfe) { + GWT.log("Could not extract cross-section db id from data."); + } + final int dbid = _dbid; + + map.put(dbid, Double.parseDouble(ce.getValue().toString())); + + disable(); + + kmService.getCrossSectionKMs(locale, map, 2, + new AsyncCallback>() { + @Override public void onFailure(Throwable caught) { - GWT.log("Could not feed artifact " + caught.getMessage()); - // TODO SC.warn + GWT.log("Could not get single km for " + + dbid + ": "+ caught.getMessage()); + SC.warn(MSG.getString(caught.getMessage())); + updateCollection(); + updateGrid(); + enable(); } - public void onSuccess(Artifact artifact) { - GWT.log("Successfully fed"); - // TODO: Also update content of spinnerbox - requestRedraw(); + @Override + public void onSuccess(Map obj) { + Double[] kms = obj.get(dbid); + double closest = + CrossSectionChartThemePanel.closest(kms, + selected_km); + GWT.log("Got single km for " + dbid + ", it is " + + closest); + SpinnerItem item = (SpinnerItem) ce.getItem(); + item.setValue(closest); + sendFeed(facetRecord, closest); } - }); + }); } }; return handler; @@ -104,6 +319,28 @@ /** + * Get the chosen km for given cross-section. + */ + final public double getKmForCrossSectionIdx(final int index) { + double km = 0d; + int collIdx = 0; + while (km == 0d) { + CollectionItem item = collection.getItem(collIdx++); + if (item == null) { + break; + } + if (item.getData() == null) continue; + String d = item.getData().get("cross_section" + index + ".km"); + if (d == null) { + continue; + } + km = Double.valueOf(d); + } + return km; + } + + + /** * Create and configure the Grid to display. */ @Override @@ -134,8 +371,10 @@ spinnerItem.setTitle("Waterlevel-Spinner"); spinnerItem.setWidth(45); // TODO actually get the value from artifact - // TODO actually get the range from artifact (or river?) - spinnerItem.setDefaultValue(0); + spinnerItem.setDefaultValue( + getKmForCrossSectionIdx(facetRecord.getTheme().getIndex())); + + // TODO actually use crosssectionkmservive spinnerItem.setMin(0); spinnerItem.setMax(1000); spinnerItem.setStep(5f); @@ -177,7 +416,6 @@ list.setShowRecordComponents(true); list.setShowRecordComponentsByCell(true); list.setShowHeader(true); - //list.setShowHeaderContextMenu(false); list.setWidth100(); list.setHeight100(); @@ -195,5 +433,17 @@ list.setFields(active, name, actions); } + + + /** Get Current Cross-section Masters uuid. */ + public String getCurrentMaster() { + return currentMasterUUID; + } + + + /** Set Current Cross-section Masters uuid. */ + public void setCurrentMaster(String currentMasterUuid) { + this.currentMasterUUID = currentMasterUuid; + } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :