Mercurial > dive4elements > river
view flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java @ 4182:ad2489b595d7
Fix for issue #967
author | Christian Lins <christian.lins@intevation.de> |
---|---|
date | Tue, 16 Oct 2012 12:44:15 +0200 |
parents | 3b4cef59836a |
children | bbd82bd8e541 |
line wrap: on
line source
package de.intevation.flys.client.client.ui.chart; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.types.ListGridFieldType; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.form.DynamicForm; 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.ChangeHandler; 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.VLayout; import com.smartgwt.client.widgets.menu.Menu; import com.smartgwt.client.widgets.menu.MenuItem; import com.smartgwt.client.widgets.menu.events.ClickHandler; import com.smartgwt.client.widgets.menu.events.MenuItemClickEvent; import de.intevation.flys.client.client.Config; import de.intevation.flys.client.client.services.CrossSectionKMServiceAsync; import de.intevation.flys.client.client.services.LoadArtifactService; import de.intevation.flys.client.client.services.LoadArtifactServiceAsync; import de.intevation.flys.client.client.ui.CollectionView; import de.intevation.flys.client.client.widgets.KMSpinner; import de.intevation.flys.client.client.widgets.KMSpinnerChangeListener; import de.intevation.flys.client.shared.model.Artifact; import de.intevation.flys.client.shared.model.Data; import de.intevation.flys.client.shared.model.DefaultArtifact; import de.intevation.flys.client.shared.model.DefaultData; 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 java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * ThemePanel much like ChartThemePanel, but shows an "Actions" column, * needed for interaction in the CrossSection Charts and a selector to * declare which cross section profile is "master" (waterlevels refer to the * chosen kilometer of that cross section profile). * Also can show 'area creation' context menus. */ public class CrossSectionChartThemePanel extends ChartThemePanel implements KMSpinnerChangeListener { /** Artifact Clone/Creation service. */ protected LoadArtifactServiceAsync loadService = GWT.create(LoadArtifactService.class); /** 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 currentCSMasterUUID; /** The layout (used for visual active/inactive feedback). */ protected VLayout layout; /** Data item name for CrossSections selected km. */ protected static String CS_KM = "cross_section.km"; /** Data item name for CrossSections selected km. */ protected static String CS_IS_MASTER = "cross_section.master?"; /** List of cross-section themes through which is moved through synchronously. */ protected HashSet synchronCrossSectionThemes = new HashSet(); /** Data for master artifact combobox.*/ protected LinkedHashMap<String, String> masters; /** Combobox for master artifacts.*/ protected SelectItem masterCb; /** * Trivial constructor. */ public CrossSectionChartThemePanel( OutputMode mode, CollectionView view) { super(mode, view); } /** Create DefaultArtifact. */ public static DefaultArtifact artifactReference(String uuid) { return new DefaultArtifact(uuid, "TODO:hash"); } /** Access data of collection item of theme. */ public static String dataOf(Theme theme, String dataItemName) { if (theme != null && theme.getCollectionItem() != null && theme.getCollectionItem().getData() != null ) { return theme.getCollectionItem().getData().get(dataItemName); } return null; } /** * 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( CS_IS_MASTER, "1"); feedService.feed( Config.getInstance().getLocale(), artifactReference(artifact), feedData, new AsyncCallback<Artifact>() { 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); setCurrentCSMaster(artifact.getUuid()); requestRedraw(); enable(); } }); } /** * Sets currentCSMasterUUID. */ public String findCurrentCSMaster() { ThemeList themeList = getThemeList(); int count = getThemeList().getThemeCount(); String firstCSUuid = null; Theme firstCS = null; for (int i = 1; i <= count; i++) { Theme theme = themeList.getThemeAt(i); String value = dataOf(theme, CS_IS_MASTER); if (value != null) { if (firstCSUuid == null) { firstCSUuid = theme.getArtifact(); firstCS = theme; } if (!value.equals("0")) { setCurrentCSMaster(theme.getArtifact()); GWT.log("found a master: " + currentCSMasterUUID + "/" + theme.getDescription()); return theme.getDescription(); } } } // There is none selected. Take the first one. if (firstCSUuid != null) { setCurrentCSMaster(firstCSUuid); feedTellMaster(firstCSUuid); } return null; } /** * Create Layout, add a master selection box beneath. */ @Override protected VLayout createLayout() { layout = super.createLayout(); // Create "set master" combobox. masterCb = new SelectItem(); masterCb.setTitle(MSG.chart_themepanel_set_master()); masterCb.setType("comboBox"); masters = getThemeList().toMapArtifactUUIDDescription("cross_section"); masterCb.setValueMap(masters); final DynamicForm form = new DynamicForm(); form.setWidth(200); form.setFields(masterCb); layout.addMember(form, 0); Config config = Config.getInstance(); final String locale = config.getLocale(); findCurrentCSMaster(); masterCb.setValue(getCurrentCSMaster()); // Add Change Handler to first unset the old master and then set the // new master. masterCb.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 (getCurrentCSMaster() != null) { Data[] feedData = DefaultData.createSimpleStringDataArray( CS_IS_MASTER, "0"); feedService.feed( locale, artifactReference(getCurrentCSMaster()), feedData, new AsyncCallback<Artifact>() { public void onFailure(Throwable caught) { GWT.log("Could not un-master artifact (" + getCurrentCSMaster() + "): " + 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). */ @Override public void disable() { this.layout.setDisabled(true); } /** DisDisable the UI (becomes ungray, responsive to user input). */ @Override 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. * @param up if true, prefer numerically higher values in case of two * values with equal distance to \param to. * @return value from in that is closest to to, -1 if none. */ public static double closest(Double[] in, double to, boolean up) { if (in == null || in.length == 0) { return -1; } if (in[0] == to) { return to; } for (int i = 0; i < in.length; i++) { GWT.log ("Close? " + in[i]); } 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; } if ((in[i] < to && up) || (in[i] > to && !up)) { continue; } double diff = Math.abs(to - in[i]); if (diff <= minDiff) { minDiff = diff; bestMatch = in[i]; } } return bestMatch; } /** * Feed a single artifact with the km of the crosssection to display. * If its the selected master, also feed the collectionmaster. * * @param artifacts List of artifacts to feed. * @param kmD The km to set. */ public void sendFeed(final List<Artifact> artifacts, final double kmD) { Config config = Config.getInstance(); final String locale = config.getLocale(); Data[] feedData = DefaultData.createSimpleStringDataArray(CS_KM, Double.valueOf(kmD).toString()); disable(); // TODO // The ones who do not have data for this km should not show line! feedService.feedMany( locale, artifacts, feedData, new AsyncCallback<List<Artifact>>() { @Override public void onFailure(Throwable caught) { GWT.log("Could not feed many artifacts " + caught.getMessage()); SC.warn(MSG.getString(caught.getMessage())); enable(); } @Override public void onSuccess(List<Artifact> artifact) { GWT.log("Successfully fed many with km"); requestRedraw(); enable(); } }); } /** * Feed a single artifact with the km of the crosssection to display. * If its the selected master, also feed the collectionmaster. * @param artUUID The UUID of the artifact to feed. * @param kmD The km to set. */ public void sendFeed(final String artUUID, final double kmD) { Config config = Config.getInstance(); final String locale = config.getLocale(); Data[] feedData = DefaultData.createSimpleStringDataArray(CS_KM, Double.valueOf(kmD).toString()); disable(); feedService.feed( locale, artifactReference(artUUID), feedData, new AsyncCallback<Artifact>() { @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 with km"); requestRedraw(); enable(); } }); } /** Remove the themes, also from the master (reference) select box. */ @Override protected void removeThemes(final ListGridRecord[] records) { // TODO take care of what happens if that was the last // cross section and/or the cross section currently selected as master. for (ListGridRecord record: records) { FacetRecord facet = (FacetRecord) record; Theme theme = facet.getTheme(); masters.remove(theme.getArtifact()); } masterCb.setValueMap(masters); super.removeThemes(records); } /** * Callback for when a value has been accepted in the km-spinner * of a Cross-Section Profile theme. * @param item The SpinnerItem which was manipulated. * @param enteredKm The double-parsed value that has been entered. * @param facetRecord The underlying datastores record. * @param up If true, numerically higher values are preferred if * two values in \param in are in the same distance to * \param to. */ public void spinnerValueEntered(KMSpinner spinner, final double enteredKm, final FacetRecord facetRecord, final boolean up) { disable(); Config config = Config.getInstance(); final String locale = config.getLocale(); Map<Integer, Double> map = new HashMap<Integer,Double>(); 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, enteredKm); // Query the available cross section measurements. kmService.getCrossSectionKMs(locale, map, 2, new AsyncCallback<Map<Integer, Double[]>>() { @Override public void onFailure(Throwable caught) { GWT.log("Could not get single km for " + dbid + ": "+ caught.getMessage()); SC.warn(MSG.getString(caught.getMessage())); updateCollection(); //updateGrid(); enable(); } @Override public void onSuccess(Map<Integer, Double[]> obj) { Double[] kms = obj.get(dbid); double closest = CrossSectionChartThemePanel.closest(kms, enteredKm, up); GWT.log("Got single km close to " + enteredKm + " for " + dbid + ", it is " + closest); // Do not set value, as it will trigger strange // "javascript" bugs. /*item.setValue(closest);*/ if (synchronCrossSectionThemes.contains (themeHash (facetRecord.getTheme()))) { // Move all other synchrons ThemeList themes = getThemeList(); int nThemes = themes.getThemeCount(); List<Artifact> artifacts = new ArrayList<Artifact>(); for (int i = 0; i < nThemes; i++) { final Theme theme = themes.getThemeAt(i+1); if (theme.getFacet().equals("cross_section") && theme.getActive() == 1 && synchronCrossSectionThemes.contains(themeHash(theme)) ) { artifacts.add(artifactReference(theme.getArtifact())); } } sendFeed(artifacts, closest); } else { sendFeed(facetRecord.getTheme().getArtifact(), closest); } } }); } /** * Create and configure the Grid to display. * @return ListGrid with Themes and related controls inside. */ @Override protected ListGrid createGrid() { final CrossSectionChartThemePanel parent = this; ListGrid list = new ListGrid() { @Override protected Canvas createRecordComponent( final ListGridRecord record, Integer colNum) { // Only cross_section Facets display an action widget. final FacetRecord facetRecord = (FacetRecord) record; if (!facetRecord.getTheme().getFacet().equals( "cross_section")) { return null; } String fieldName = this.getFieldName(colNum); if (fieldName.equals(GRID_FIELD_ACTIONS)) { double currentValue = Double.valueOf(facetRecord.getTheme().getCollectionItem().getData().get(CS_KM)); KMSpinner kmSpinner = new KMSpinner(currentValue, facetRecord); kmSpinner.addChangeListener(parent); return kmSpinner; } else { return null; } } }; list.setCanResizeFields(true); list.setShowRecordComponents(true); list.setShowRecordComponentsByCell(true); list.setShowAllRecords(true); list.setShowHeaderContextMenu(false); list.setLeaveScrollbarGap(false); return list; } /** * Initializes the components (columns) of the theme grid. */ @Override protected void initGrid() { list.setCanEdit(true); list.setCanSort(false); list.setShowRecordComponents(true); list.setShowRecordComponentsByCell(true); list.setShowHeader(true); list.setWidth100(); list.setHeight100(); list.addEditCompleteHandler(this); ListGridField active = new ListGridField(GRID_FIELD_ACTIVE, " ", 20); active.setType(ListGridFieldType.BOOLEAN); ListGridField name = new ListGridField( GRID_FIELD_NAME, MSG.chart_themepanel_header_themes()); name.setType(ListGridFieldType.TEXT); ListGridField actions = new ListGridField(GRID_FIELD_ACTIONS, MSG.chart_themepanel_header_actions(), 100); list.setFields(active, name, actions); } /** Get Current Cross-section Masters uuid. */ public String getCurrentCSMaster() { return currentCSMasterUUID; } /** Set Current Cross-section Masters uuid. */ public void setCurrentCSMaster(String currentMasterUuid) { this.currentCSMasterUUID = currentMasterUuid; } /** Returns name of cross section area facets. */ @Override protected String getAreaFacetName() { return "cross_section.area"; } /** * Return true if two themes are canditates for an area being * rendered between them. * TODO join with canArea, generalize to allow easier modification * in subclasses. */ @Override protected boolean areAreaCompatible(Theme a, Theme b) { if (a.equals(b)) { return false; } return (a.getFacet().equals("cross_section") || a.getFacet().endsWith("line")) && (b.getFacet().equals("cross_section") || b.getFacet().endsWith("line")); } /** * True if context menu should contain 'create area' submenu on * this theme. */ @Override protected boolean canArea(Theme a) { return a.getFacet().equals("cross_section") || a.getFacet().equals("cross_section_water_line") || a.getFacet().endsWith("line"); } protected String themeHash(Theme theme) { return theme.getArtifact() + theme.getFacet() + theme.getIndex(); } /** * Include synchron navigation item. */ @Override protected Menu getSingleContextMenu(final ListGridRecord[] records) { Menu contextMenu = super.getSingleContextMenu(records); Theme facetTheme = ((FacetRecord)records[0]).getTheme(); String item = facetTheme.getFacet(); if (item.equals("cross_section")) { // Synchron checking. MenuItem synchronNavigationMenuItem = new MenuItem(); final String themeHash = themeHash(facetTheme); if (synchronCrossSectionThemes.contains(themeHash)) { synchronNavigationMenuItem.setTitle(MSG.chart_themepanel_asynchron()); synchronNavigationMenuItem.addClickHandler(new ClickHandler() { public void onClick(MenuItemClickEvent evt) { synchronCrossSectionThemes.remove (themeHash); } }); } else { synchronNavigationMenuItem.setTitle(MSG.chart_themepanel_synchron()); synchronNavigationMenuItem.addClickHandler(new ClickHandler() { public void onClick(MenuItemClickEvent evt) { synchronCrossSectionThemes.add (themeHash); } }); } contextMenu.addItem(synchronNavigationMenuItem); } return contextMenu; } /** * This method is used to clear the current theme grid and add new updated * data. */ @Override protected void updateGrid() { GWT.log("CrossSectionChartThemePanel.updateGrid"); ListGridRecord[] selected = list.getSelectedRecords(); clearGrid(); ThemeList themeList = getThemeList(); if (themeList == null) { GWT.log("ERROR: No theme list."); return; } int count = themeList.getThemeCount(); for (int i = 1; i <= count; i++) { Theme theme = themeList.getThemeAt(i); if (theme == null) { continue; } if (theme.getFacet().equals("empty.facet")) { theme.setVisible(0); } if (theme.getVisible() == 0) { continue; } if (theme.getFacet().equals("cross_section")) { addToReferences(theme); } FacetRecord newRecord = createRecord(theme); addFacetRecord(newRecord); String newArtifact = theme.getArtifact(); String newFacet = theme.getFacet(); int newIndex = theme.getIndex(); for (ListGridRecord r: selected) { FacetRecord sel = (FacetRecord) r; Theme oldTheme = sel.getTheme(); if (oldTheme.getArtifact().equals(newArtifact) && oldTheme.getFacet().equals(newFacet) && oldTheme.getIndex() == newIndex) { list.selectRecord(newRecord); } } } fireOutputParameterChanged(); } /** * Adds a cross section theme to the master artifacts combobox and finds * a new master if necessary. * * @param theme The cross section theme. */ protected void addToReferences(Theme theme) { if (theme.getVisible() != 0) { masters.put(theme.getArtifact(), theme.getDescription()); masterCb.setValueMap(masters); } findCurrentCSMaster(); if (masterCb.getSelectedRecord() == null) { masterCb.setValue(getCurrentCSMaster()); } } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :