view gwt-client/src/main/java/org/dive4elements/river/client/client/ui/LocationDistancePanel.java @ 9416:05405292a7ca

Navigationtheme panel now shows themes of dWt and WQ charts grayed out, if the current station is outside the valid range of the theme.
author gernotbelger
date Thu, 16 Aug 2018 16:28:03 +0200
parents 7337034eb5d5
children
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.client.client.ui;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import org.dive4elements.river.client.client.Config;
import org.dive4elements.river.client.client.FLYSConstants;
import org.dive4elements.river.client.client.event.FilterHandler;
import org.dive4elements.river.client.client.event.RangeFilterEvent;
import org.dive4elements.river.client.client.event.StringFilterEvent;
import org.dive4elements.river.client.client.services.DistanceInfoService;
import org.dive4elements.river.client.client.services.DistanceInfoServiceAsync;
import org.dive4elements.river.client.client.ui.range.DistanceInfoDataSource;
import org.dive4elements.river.client.shared.model.ArtifactDescription;
import org.dive4elements.river.client.shared.model.Data;
import org.dive4elements.river.client.shared.model.DataItem;
import org.dive4elements.river.client.shared.model.DataList;
import org.dive4elements.river.client.shared.model.DefaultData;
import org.dive4elements.river.client.shared.model.DefaultDataItem;
import org.dive4elements.river.client.shared.model.DistanceInfoObject;

import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.NumberFormat;
import com.smartgwt.client.data.AdvancedCriteria;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.Criterion;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.types.OperatorId;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.RadioGroupItem;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.form.fields.events.BlurEvent;
import com.smartgwt.client.widgets.form.fields.events.BlurHandler;
import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.grid.CellFormatter;
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.grid.events.RecordClickEvent;
import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.tab.Tab;
import com.smartgwt.client.widgets.tab.TabSet;
import com.smartgwt.client.widgets.tab.events.TabSelectedEvent;
import com.smartgwt.client.widgets.tab.events.TabSelectedHandler;

/**
 * This UIProvider creates a widget to enter locations or a distance.
 *
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class LocationDistancePanel extends AbstractUIProvider implements ChangeHandler, BlurHandler, FilterHandler {
    private static final long serialVersionUID = -10820092176039372L;

    /** The message class that provides i18n strings. */
    protected FLYSConstants MESSAGES = GWT.create(FLYSConstants.class);

    /** The DistanceInfoService used to retrieve locations about rivers. */
    protected DistanceInfoServiceAsync distanceInfoService = GWT.create(DistanceInfoService.class);

    public static final String FIELD_MODE = "mode";

    /** The constant name of the input field to enter the start of a distance. */
    public static final String FIELD_FROM = "from";

    /** The constant name of the input field to enter the end of a distance. */
    public static final String FIELD_TO = "to";

    /** The constant name of the input field to enter locations. */
    public static final String FIELD_VALUE_LOCATION = "location";

    /** The constant name of the input field to enter distance. */
    public static final String FIELD_VALUE_DISTANCE = "distance";

    /**
     * The constant name of the input field to enter the step width of a
     * distance.
     */
    public static final String FIELD_WIDTH = "width";

    public static final int WIDTH = 250;

    /** The radio group for input mode selection. */
    protected DynamicForm mode;

    /** A container that will contain the location or the distance panel. */
    protected HLayout container;

    /** The min value for a distance. */
    protected double min;

    /** The max value for a distance. */
    protected double max;

    /** The 'from' value entered in the distance mode. */
    protected double from;

    /** The 'to' value entered in the distance mode. */
    protected double to;

    /** The 'step' value entered in the distance mode. */
    protected double step;

    /** The values entered in the location mode. */
    protected double[] values;

    /** The input panel for locations. */
    protected DoubleArrayPanel locationPanel;

    /** The input panel for distances. */
    protected DoubleRangePanel distancePanel;

    /** The tab set containing the location and distance table. */
    protected TabSet inputTables;

    /** The distance table. */
    protected ListGrid distanceTable;

    /** The locations table. */
    protected ListGrid locationsTable;

    /** The table data. */
    protected DistanceInfoObject[] tableData;

    /** The table filter. */
    protected TableFilter filterDescription;
    protected RangeTableFilter filterRange;

    /** The Combobox for table filter criteria. */
    protected SelectItem filterCriteria;
    protected StaticTextItem filterResultCount;
    protected ListGrid currentFiltered;

    /**
     * Creates a new LocationDistancePanel instance.
     */
    public LocationDistancePanel() {
        this.distanceTable = new ListGrid();
        this.distanceTable.setAutoFetchData(true);

        this.locationsTable = new ListGrid();
        this.locationsTable.setAutoFetchData(true);

        this.distanceTable.setShowHeaderContextMenu(false);
        this.locationsTable.setShowHeaderContextMenu(false);
    }

    /**
     * This method creates a widget that contains a label, a panel with
     * checkboxes to switch the input mode between location and distance input,
     * and a the mode specific panel.
     *
     * @param data
     *            The data that might be inserted.
     *
     * @return a panel.
     */
    @Override
    public Canvas create(final DataList data) {
        final VLayout layout = new VLayout();
        layout.setMembersMargin(10);

        final Label label = getLabel(data);// new Label(MESSAGES.location_distance_state());
        final Canvas widget = createWidget(data);
        final Canvas submit = getNextButton();
        createDistanceInputPanel();

        initDefaults(data);

        widget.setHeight(50);
        label.setHeight(25);

        layout.addMember(label);
        layout.addMember(widget);
        layout.addMember(submit);

        return layout;
    }

    protected Label getLabel(final DataList data) {
        final Data item = data.get(0);
        return new Label(item.getDescription());//
        // TODO: migrate distance_part_state to server
        // item.getDescription()); // holt das Label vom Server (funktoniert schon sehr oft so!)
    }

    /**
     * Setup a table for a DistanceInfoDataSource.
     *
     * Sets up a table to for input completion. The table
     * can be used either for single locations or distances.
     * Depending on the value of isDistance the table will
     * have a to and a from column or a single location column.
     *
     * @param table
     *            the ListGrid to set up.
     * @param doublePins
     *            wether or not to have.
     * @param isDistance
     *            wether or not to and from should be included.
     */
    protected void setupDistanceInfoTable(final ListGrid table, final boolean doublePins, final boolean isDistance) {

        final String baseUrl = GWT.getHostPageBaseURL();

        table.setWidth100();
        table.setShowRecordComponents(true);
        table.setShowRecordComponentsByCell(true);
        table.setHeight100();
        table.setEmptyMessage(this.MESSAGES.empty_filter());
        table.setCanReorderFields(false);

        final CellFormatter cf = new CellFormatter() {
            @Override
            public String format(final Object value, final ListGridRecord record, final int rowNum, final int colNum) {
                if (value == null)
                    return null;
                try {
                    NumberFormat nf;
                    final double v = Double.parseDouble((String) value);
                    nf = NumberFormat.getFormat("###0.00##");
                    return nf.format(v);
                }
                catch (final Exception e) {
                    return value.toString();
                }
            }
        };
        ListGridField pin1 = null;
        ListGridField pin2 = null;

        if (doublePins) {
            pin1 = new ListGridField("fromIcon", this.MESSAGES.from());
            pin1.setWidth(30);
        } else {
            pin1 = new ListGridField("fromIcon", this.MESSAGES.selection());
            pin1.setWidth(60);
        }
        pin1.setType(ListGridFieldType.ICON);
        pin1.setCellIcon(baseUrl + this.MESSAGES.markerGreen());

        if (doublePins) {
            pin2 = new ListGridField("toIcon", this.MESSAGES.to());
            pin2.setType(ListGridFieldType.ICON);
            pin2.setWidth(30);
            pin2.setCellIcon(baseUrl + this.MESSAGES.markerRed());
        }

        if (isDistance) {
            /* We have from / to fields */
            pin1.addRecordClickHandler(new RecordClickHandler() {
                @Override
                public void onRecordClick(final RecordClickEvent e) {
                    final Record r = e.getRecord();
                    if (!isLocationMode()) {
                        /* distance panel and distance mode */
                        setFrom(r.getAttribute("from"));
                        setTo(r.getAttribute("to"));
                    } else {
                        /* distance panel and location mode */
                        /* Pin 1 is the "from" pin */
                        appendLocation(r.getAttribute("from"));
                    }
                }
            });
            if (doublePins) {
                pin2.addRecordClickHandler(new RecordClickHandler() {
                    @Override
                    public void onRecordClick(final RecordClickEvent e) {
                        final Record r = e.getRecord();
                        if (isLocationMode()) {
                            appendLocation(r.getAttribute("to"));
                        } else {
                            /*
                             * Distance and double pin behavior is only
                             * defined for location mode.
                             */
                            GWT.log("Unhandled input state.");
                        }
                    }
                });
            }
        } else {
            /* We only have the from field */
            pin1.addRecordClickHandler(new RecordClickHandler() {
                @Override
                public void onRecordClick(final RecordClickEvent e) {
                    final Record r = e.getRecord();
                    if (!isLocationMode()) {
                        /* Location panel and distance mode */
                        setFrom(r.getAttribute("from"));
                    } else {
                        /* Location panel and location mode */
                        appendLocation(r.getAttribute("from"));
                    }
                }
            });
            if (doublePins) {
                pin2.addRecordClickHandler(new RecordClickHandler() {
                    @Override
                    public void onRecordClick(final RecordClickEvent e) {
                        final Record r = e.getRecord();
                        if (!isLocationMode()) {
                            setTo(r.getAttribute("from"));
                        } else {
                            /*
                             * Distance and double pin behavior is only
                             * defined for location mode.
                             */
                            GWT.log("Unhandled input state.");
                        }
                    }
                });
            }
        }

        final ListGridField ddescr = new ListGridField("description", this.MESSAGES.description());
        ddescr.setType(ListGridFieldType.TEXT);
        ddescr.setWidth("*");

        ListGridField from;
        ListGridField to = null;

        if (isDistance) {
            from = new ListGridField("from", this.MESSAGES.from());
            to = new ListGridField("to", this.MESSAGES.to());
            to.setType(ListGridFieldType.FLOAT);
            to.setCellFormatter(cf);

            to.setWidth("12%");
            to.setAlign(Alignment.LEFT);
        } else {
            from = new ListGridField("from", this.MESSAGES.locations());
        }
        from.setCellFormatter(cf);
        from.setWidth("12%");

        final ListGridField dside = new ListGridField("riverside", this.MESSAGES.riverside());
        dside.setType(ListGridFieldType.TEXT);
        dside.setWidth("12%");

        final ListGridField bottom = new ListGridField("bottom", this.MESSAGES.bottom_edge());
        bottom.setType(ListGridFieldType.TEXT);
        bottom.setWidth("10%");
        bottom.setCellFormatter(cf);

        final ListGridField top = new ListGridField("top", this.MESSAGES.top_edge());
        top.setType(ListGridFieldType.TEXT);
        top.setWidth("10%");
        top.setCellFormatter(cf);

        if (doublePins && isDistance) {
            table.setFields(pin1, pin2, ddescr, from, to, dside, bottom, top);
        } else if (doublePins) {
            table.setFields(pin1, pin2, ddescr, from, dside, bottom, top);
        } else if (isDistance) {
            table.setFields(pin1, ddescr, from, to, dside, bottom, top);
        } else {
            table.setFields(pin1, ddescr, from, dside, bottom, top);
        }
    }

    @Override
    public Canvas createOld(final DataList dataList) {
        final List<Data> items = dataList.getAll();

        final Data dMode = getData(items, "ld_mode");
        final DataItem[] dItems = dMode.getItems();

        boolean rangeMode = true;
        if (dItems != null && dItems[0] != null) {
            rangeMode = FIELD_VALUE_DISTANCE.equals(dItems[0].getStringValue());
        }

        final HLayout layout = new HLayout();
        layout.setWidth("400px");

        final Label label = new Label(dataList.getLabel());
        label.setWidth("200px");

        final Canvas back = getBackButton(dataList.getState());

        layout.addMember(label);

        if (rangeMode) {
            layout.addMember(getOldRangeSelection(dataList));
        } else {
            layout.addMember(getOldLocationSelection(dataList));
        }

        layout.addMember(back);

        return layout;
    }

    /**
     * Creates a label for the selected range.
     *
     * @param dataList
     *            The DataList containing all values for this state.
     *
     * @return A label displaying the selected values.
     */
    protected Label getOldRangeSelection(final DataList dataList) {
        final List<Data> items = dataList.getAll();

        final Data dFrom = getData(items, "ld_from");
        final Data dTo = getData(items, "ld_to");
        final Data dStep = getData(items, "ld_step");

        final DataItem[] from = dFrom.getItems();
        final DataItem[] to = dTo.getItems();
        final DataItem[] step = dStep.getItems();

        final StringBuilder sb = new StringBuilder();
        sb.append(from[0].getLabel());
        sb.append(" " + this.MESSAGES.unitFrom() + " ");
        sb.append(to[0].getLabel());
        sb.append(" " + this.MESSAGES.unitTo() + " ");
        sb.append(step[0].getLabel());
        sb.append(" " + this.MESSAGES.unitWidth());

        final Label selected = new Label(sb.toString());
        selected.setWidth("130px");

        return selected;
    }

    /**
     * Creates a label for the selected locations.
     *
     * @param dataList
     *            The DataList containing all values for this state.
     *
     * @return A label displaying the selected values.
     */
    protected Label getOldLocationSelection(final DataList dataList) {
        final List<Data> items = dataList.getAll();

        final Data dLocations = getData(items, "ld_locations");
        final DataItem[] lItems = dLocations.getItems();

        final String[] splitted = StringArrayParseHelper.getArrayFromRawString(lItems[0].getStringValue());
        String value = "";
        for (final String element : splitted) {
            try {
                final NumberFormat nf = NumberFormat.getDecimalFormat();
                final double dv = Double.parseDouble(element);
                value += nf.format(dv) + " ";
            }
            catch (final NumberFormatException nfe) {
                value += element + " ";
            }
        }

        final Label selected = new Label(value);
        selected.setWidth(130);

        return selected;
    }

    /**
     * This method reads the default values defined in the DataItems of the Data
     * objects in <i>list</i>.
     *
     * @param list
     *            The DataList container that stores the Data objects.
     */
    protected void initDefaults(final DataList list) {
        final Data m = getData(list.getAll(), "ld_mode");
        final Data l = getData(list.getAll(), "ld_locations");
        final Data f = getData(list.getAll(), "ld_from");
        final Data t = getData(list.getAll(), "ld_to");
        final Data s = getData(list.getAll(), "ld_step");

        final DataItem[] fItems = f.getItems();
        final DataItem[] tItems = t.getItems();
        final DataItem[] sItems = s.getItems();

        this.min = Double.valueOf(fItems[0].getStringValue());
        this.max = Double.valueOf(tItems[0].getStringValue());
        this.step = Double.valueOf(sItems[0].getStringValue());

        final DataItem mDef = m.getDefault();
        final DataItem lDef = l.getDefault();
        final DataItem fDef = f.getDefault();
        final DataItem tDef = t.getDefault();
        final DataItem sDef = s.getDefault();

        final String mDefValue = mDef != null ? mDef.getStringValue() : null;
        final String theMode = mDefValue != null && mDefValue.length() > 0 ? mDef.getStringValue() : FIELD_VALUE_DISTANCE;

        this.mode.setValue(FIELD_MODE, theMode);

        final String fDefValue = fDef != null ? fDef.getStringValue() : null;
        setFrom(fDefValue != null && fDefValue.length() > 0 ? Double.valueOf(fDef.getStringValue()) : this.min);

        final String tDefValue = tDef != null ? tDef.getStringValue() : null;
        setTo(tDefValue != null && tDefValue.length() > 0 ? Double.valueOf(tDef.getStringValue()) : this.max);

        final String sDefValue = sDef != null ? sDef.getStringValue() : null;
        setStep(sDefValue != null && sDefValue.length() > 0 ? Double.valueOf(sDef.getStringValue()) : this.step);

        if (lDef != null) {
            final String lDefValue = lDef != null ? lDef.getStringValue() : null;

            if (lDefValue != null && lDefValue.length() > 0) {
                setLocationValues(lDef.getStringValue());
            }
        }

        if (theMode.equals(FIELD_VALUE_DISTANCE)) {
            enableDistanceMode();
            this.inputTables.selectTab(1);
        } else {
            enableLocationMode();
        }
        this.currentFiltered = (ListGrid) this.inputTables.getSelectedTab().getPane();

        this.distancePanel.setValues(getFrom(), getTo(), getStep());
    }

    protected Canvas createWidget(final DataList data) {
        final VLayout layout = new VLayout();
        this.container = new HLayout();
        final Canvas checkboxPanel = createRadioButtonPanel();

        this.locationPanel = new DoubleArrayPanel(this.MESSAGES.unitLocation(), getLocationValues(), this);

        this.distancePanel = new DoubleRangePanel(this.MESSAGES.unitFrom(), this.MESSAGES.unitTo(), this.MESSAGES.unitWidth(), 0, 0, 0, /*
                                                                                                                                         * initDefaults set the
                                                                                                                                         * default values for
                                                                                                                                         * this.
                                                                                                                                         */
                400, this);

        this.container.addMember(this.locationPanel);
        this.container.addMember(this.distancePanel);
        this.container.hideMember(this.locationPanel);

        layout.addMember(checkboxPanel);
        layout.addMember(this.container);

        this.container.setMembersMargin(30);

        this.inputTables = new TabSet();
        this.inputTables.addTabSelectedHandler(new TabSelectedHandler() {
            @Override
            public void onTabSelected(final TabSelectedEvent evt) {
                LocationDistancePanel.this.filterDescription.clear();
                LocationDistancePanel.this.filterRange.clear();
                LocationDistancePanel.this.filterResultCount.setValue("");

                // The assumption is that location is tab 0 and distance tab 1

                final Canvas c = evt.getTabPane();
                if (c instanceof ListGrid) {
                    LocationDistancePanel.this.currentFiltered = (ListGrid) c;
                }
            }
        });

        final Tab locations = new Tab(this.MESSAGES.locations());
        final Tab distances = new Tab(this.MESSAGES.distance());

        this.inputTables.setWidth100();
        this.inputTables.setHeight100();

        locations.setPane(this.locationsTable);
        distances.setPane(this.distanceTable);

        this.inputTables.addTab(locations);
        this.inputTables.addTab(distances);

        this.filterResultCount = new StaticTextItem(this.MESSAGES.resultCount());
        this.filterResultCount.setTitleAlign(Alignment.LEFT);
        this.filterResultCount.setTitleStyle("color: #000");

        this.filterDescription = new TableFilter();
        this.filterDescription.setHeight("30px");
        this.filterDescription.addFilterHandler(this);

        this.filterRange = new RangeTableFilter();
        this.filterRange.setHeight("30px");
        this.filterRange.addFilterHandler(this);
        this.filterRange.setVisible(false);

        this.filterCriteria = new SelectItem();
        this.filterCriteria.setShowTitle(false);
        this.filterCriteria.setWidth(100);
        this.filterCriteria.addChangedHandler(new ChangedHandler() {
            @Override
            public void onChanged(final ChangedEvent e) {
                if (e.getValue().toString().equals("range")) {
                    LocationDistancePanel.this.filterRange.setVisible(true);
                    LocationDistancePanel.this.filterDescription.setVisible(false);
                    LocationDistancePanel.this.filterDescription.clear();
                    LocationDistancePanel.this.filterResultCount.setValue("");
                } else {
                    LocationDistancePanel.this.filterRange.setVisible(false);
                    LocationDistancePanel.this.filterRange.clear();
                    LocationDistancePanel.this.filterDescription.setVisible(true);
                    LocationDistancePanel.this.filterResultCount.setValue("");
                }
            }
        });

        final LinkedHashMap<String, String> filterMap = new LinkedHashMap<String, String>();
        filterMap.put("description", this.MESSAGES.description());
        filterMap.put("range", this.MESSAGES.range());
        this.filterCriteria.setValueMap(filterMap);
        this.filterCriteria.setValue("description");

        final DynamicForm form = new DynamicForm();
        form.setFields(this.filterCriteria);
        this.inputTables.setHeight("*");
        final DynamicForm form2 = new DynamicForm();
        form2.setFields(this.filterResultCount);

        final VLayout helper = new VLayout();
        final HLayout filterLayout = new HLayout();

        filterLayout.addMember(form);
        filterLayout.addMember(this.filterDescription);
        filterLayout.addMember(this.filterRange);
        filterLayout.setHeight("30px");
        helper.addMember(this.inputTables);
        helper.addMember(filterLayout);
        helper.addMember(form2);
        helper.setHeight100();
        helper.setWidth100();

        this.helperContainer.addMember(helper);
        filterLayout.setWidth("200");

        return layout;
    }

    @Override
    public void onFilterCriteriaChanged(final StringFilterEvent event) {
        final String search = event.getFilter();

        if (search != null && search.length() > 0) {
            final Criteria c = new Criteria("description", search);

            this.locationsTable.filterData(c);
            this.distanceTable.filterData(c);
            this.filterResultCount.setValue(this.currentFiltered.getRecords().length);
        } else {
            this.locationsTable.clearCriteria();
            this.distanceTable.clearCriteria();
            this.filterResultCount.setValue("");
        }
    }

    @Override
    public void onFilterCriteriaChanged(final RangeFilterEvent event) {
        final Float from = event.getFrom() - 0.001f;
        final Float to = event.getTo() + 0.001f;
        GWT.log("filtering range: " + from + " to " + to);

        Criterion combinedFilter = null;
        Criterion locationFilter = null;
        if (from.equals(Float.NaN) && to.equals(Float.NaN)) {
            this.locationsTable.clearCriteria();
            this.distanceTable.clearCriteria();
            this.filterResultCount.setValue("");
            return;
        } else if (from.equals(Float.NaN)) {
            combinedFilter = new Criterion("to", OperatorId.LESS_OR_EQUAL, to);
            locationFilter = new Criterion("from", OperatorId.LESS_OR_EQUAL, to);
            this.locationsTable.filterData(locationFilter);
            this.distanceTable.filterData(combinedFilter);
            this.filterResultCount.setValue(this.currentFiltered.getRecords().length);
            return;
        } else if (to.equals(Float.NaN)) {
            combinedFilter = new Criterion("from", OperatorId.GREATER_OR_EQUAL, from);
            this.locationsTable.filterData(combinedFilter);
            this.distanceTable.filterData(combinedFilter);
        } else {
            final AdvancedCriteria c1 = new AdvancedCriteria(OperatorId.AND,
                    new Criterion[] { new Criterion("from", OperatorId.GREATER_OR_EQUAL, from), new Criterion("from", OperatorId.LESS_OR_EQUAL, to) });

            final AdvancedCriteria c2 = new AdvancedCriteria(OperatorId.AND,
                    new Criterion[] { new Criterion("to", OperatorId.GREATER_OR_EQUAL, from), new Criterion("to", OperatorId.LESS_OR_EQUAL, to) });

            final AdvancedCriteria c3 = new AdvancedCriteria(OperatorId.AND,
                    new Criterion[] { new Criterion("from", OperatorId.LESS_OR_EQUAL, to), new Criterion("to", OperatorId.GREATER_OR_EQUAL, from) });

            combinedFilter = new AdvancedCriteria(OperatorId.OR, new Criterion[] { c1, c2, c3 });
        }
        this.locationsTable.filterData(combinedFilter);
        this.distanceTable.filterData(combinedFilter);
        this.filterResultCount.setValue(this.currentFiltered.getRecords().length);
    }

    @Override
    public List<String> validate() {
        if (isLocationMode()) {
            return validateLocations();
        } else {
            return validateRange();
        }
    }

    protected List<String> validateLocations() {
        final List<String> errors = new ArrayList<String>();
        final NumberFormat nf = NumberFormat.getDecimalFormat();

        try {
            saveLocationValues(this.locationPanel);
        }
        catch (final Exception e) {
            errors.add(this.MESSAGES.wrongFormat());
        }

        final double[] values = getLocationValues();
        final double[] good = new double[values.length];
        int idx = 0;

        for (final double value : values) {
            if (value < this.min || value > this.max) {
                String tmp = this.MESSAGES.error_validate_range();
                tmp = tmp.replace("$1", nf.format(value));
                tmp = tmp.replace("$2", nf.format(this.min));
                tmp = tmp.replace("$3", nf.format(this.max));
                errors.add(tmp);
            } else {
                good[idx++] = value;
            }
        }

        final double[] justGood = new double[idx];
        for (int i = 0; i < justGood.length; i++) {
            justGood[i] = good[i];
        }

        if (!errors.isEmpty()) {
            this.locationPanel.setValues(justGood);
        }

        return errors;
    }

    protected List<String> validateRange() {
        final List<String> errors = new ArrayList<String>();
        final NumberFormat nf = NumberFormat.getDecimalFormat();

        try {
            saveDistanceValues(this.distancePanel);
        }
        catch (final Exception e) {
            errors.add(this.MESSAGES.wrongFormat());
        }

        double from = getFrom();
        double to = getTo();
        final double step = getStep();

        if (from < this.min || from > this.max) {
            String tmp = this.MESSAGES.error_validate_range();
            tmp = tmp.replace("$1", nf.format(from));
            tmp = tmp.replace("$2", nf.format(this.min));
            tmp = tmp.replace("$3", nf.format(this.max));
            errors.add(tmp);
            from = this.min;
        }

        if (to < this.min || to > this.max) {
            String tmp = this.MESSAGES.error_validate_range();
            tmp = tmp.replace("$1", nf.format(to));
            tmp = tmp.replace("$2", nf.format(this.min));
            tmp = tmp.replace("$3", nf.format(this.max));
            errors.add(tmp);
            to = this.max;
        }

        if (!errors.isEmpty()) {
            this.distancePanel.setValues(from, to, step);
        }

        return errors;
    }

    /**
     * This method returns the selected data.
     *
     * @return the selected/inserted data.
     */
    @Override
    public Data[] getData() {
        final List<Data> data = new ArrayList<Data>();

        // If we have entered a value and click right afterwards on the
        // 'next' button, the BlurEvent is not fired, and the values are not
        // saved. So, we gonna save those values explicitly.
        if (isLocationMode()) {
            final Canvas member = this.container.getMember(0);
            if (member instanceof DoubleArrayPanel) {
                final DoubleArrayPanel form = (DoubleArrayPanel) member;
                saveLocationValues(form);
            }

            final Data dLocations = getDataLocations();
            final DataItem dFrom = new DefaultDataItem("ld_from", "ld_from", "");
            final DataItem dTo = new DefaultDataItem("ld_to", "ld_to", "");
            final DataItem dStep = new DefaultDataItem("ld_step", "ld_step", "");

            data.add(dLocations);
            data.add(new DefaultData("ld_from", null, null, new DataItem[] { dFrom }));
            data.add(new DefaultData("ld_to", null, null, new DataItem[] { dTo }));
            data.add(new DefaultData("ld_step", null, null, new DataItem[] { dStep }));
        } else {
            final Canvas member = this.container.getMember(0);
            if (member instanceof DoubleRangePanel) {
                final DoubleRangePanel form = (DoubleRangePanel) member;
                saveDistanceValues(form);
            }

            final Data dFrom = getDataFrom();
            final Data dTo = getDataTo();
            final Data dStep = getDataStep();
            final DataItem loc = new DefaultDataItem("ld_locations", "ld_locations", "");

            data.add(dFrom);
            data.add(dTo);
            data.add(dStep);
            data.add(new DefaultData("ld_locations", null, null, new DataItem[] { loc }));
        }

        final Data dMode = getDataMode();
        if (dMode != null) {
            data.add(dMode);
        }

        return data.toArray(new Data[data.size()]);
    }

    /**
     * Returns the Data object for the 'mode' attribute.
     *
     * @return the Data object for the 'mode' attribute.
     */
    protected Data getDataMode() {
        final String value = this.mode.getValueAsString(FIELD_MODE);
        final DataItem item = new DefaultDataItem("ld_mode", "ld_mode", value);
        return new DefaultData("ld_mode", null, null, new DataItem[] { item });
    }

    protected Data getDataLocations() {
        final double[] locations = getLocationValues();
        boolean first = true;

        if (locations == null) {
            return null;
        }

        final StringBuilder sb = new StringBuilder();

        for (final double l : locations) {
            if (!first) {
                sb.append(" ");
            }

            sb.append(l);

            first = false;
        }

        final DataItem item = new DefaultDataItem("ld_locations", "ld_locations", sb.toString());

        return new DefaultData("ld_locations", null, null, new DataItem[] { item });
    }

    /**
     * Returns the Data object for the 'from' attribute.
     *
     * @return the Data object for the 'from' attribute.
     */
    protected Data getDataFrom() {
        final String value = Double.valueOf(getFrom()).toString();
        final DataItem item = new DefaultDataItem("ld_from", "ld_from", value);
        return new DefaultData("ld_from", null, null, new DataItem[] { item });
    }

    /**
     * Returns the Data object for the 'to' attribute.
     *
     * @return the Data object for the 'to' attribute.
     */
    protected Data getDataTo() {
        final String value = Double.valueOf(getTo()).toString();
        final DataItem item = new DefaultDataItem("ld_to", "ld_to", value);
        return new DefaultData("ld_to", null, null, new DataItem[] { item });
    }

    /**
     * Returns the Data object for the 'step' attribute.
     *
     * @return the Data object for the 'step' attribute.
     */
    protected Data getDataStep() {
        final String value = Double.valueOf(getStep()).toString();
        final DataItem item = new DefaultDataItem("ld_step", "ld_step", value);
        return new DefaultData("ld_step", null, null, new DataItem[] { item });
    }

    /**
     * Determines the current input mode.
     *
     * @return true, if 'location' is the current input mode, otherwise false.
     */
    public boolean isLocationMode() {
        final String inputMode = this.mode.getValueAsString(FIELD_MODE);

        return inputMode.equals(FIELD_VALUE_LOCATION) ? true : false;
    }

    /**
     * Activates the location panel.
     */
    protected void enableLocationMode() {
        this.mode.setValue(FIELD_MODE, FIELD_VALUE_LOCATION);
        this.container.hideMember(this.distancePanel);
        this.container.showMember(this.locationPanel);
        setupDistanceInfoTable(this.locationsTable, false, false);
        setupDistanceInfoTable(this.distanceTable, true, true);
        this.inputTables.updateTab(0, this.locationsTable);
        this.inputTables.updateTab(1, this.distanceTable);
    }

    /**
     * Activates the distance panel.
     */
    protected void enableDistanceMode() {
        this.mode.setValue(FIELD_MODE, FIELD_VALUE_DISTANCE);
        this.container.hideMember(this.locationPanel);
        this.container.showMember(this.distancePanel);
        setupDistanceInfoTable(this.locationsTable, true, false);
        setupDistanceInfoTable(this.distanceTable, false, true);
        this.inputTables.updateTab(0, this.locationsTable);
        this.inputTables.updateTab(1, this.distanceTable);
    }

    /**
     * This method switches the input mode between location and distance input.
     *
     * @param event
     *            The click event fired by a RadioButtonGroupItem.
     */
    @Override
    public void onChange(final ChangeEvent event) {
        final String value = (String) event.getValue();

        if (value == null) {
            return;
        }
        if (value.equals(FIELD_VALUE_LOCATION)) {
            enableLocationMode();
            this.filterDescription.clear();
            this.filterRange.clear();
            this.filterResultCount.setValue("");

            // Bring this tab to front.
            this.inputTables.selectTab(0);
        } else {
            enableDistanceMode();
            this.filterDescription.clear();
            this.filterRange.clear();
            this.filterResultCount.setValue("");

            // Bring the distanceTable tab to front.
            this.inputTables.selectTab(1);
        }
    }

    /**
     * This method is used to validate the inserted data in the form fields.
     *
     * @param event
     *            The BlurEvent that gives information about the FormItem that
     *            has been modified and its value.
     */
    @Override
    public void onBlur(final BlurEvent event) {
        final FormItem item = event.getItem();
        final String field = item.getFieldName();

        if (field == null) {
            return;
        }

        if (field.equals(DoubleArrayPanel.FIELD_NAME)) {
            final DoubleArrayPanel p = (DoubleArrayPanel) event.getForm();

            saveLocationValue(p, item);
        } else {
            final DoubleRangePanel p = (DoubleRangePanel) event.getForm();

            saveDistanceValue(p, item);
        }
    }

    /**
     * Validates and stores all values entered in the location mode.
     *
     * @param p
     *            The DoubleArrayPanel.
     */
    protected void saveLocationValues(final DoubleArrayPanel p) {
        final FormItem[] formItems = p.getFields();

        for (final FormItem item : formItems) {
            if (item.getFieldName().equals(DoubleArrayPanel.FIELD_NAME)) {
                saveLocationValue(p, item);
            }
        }
    }

    /**
     * Validates and stores all values entered in the distance mode.
     *
     * @param p
     *            The DoubleRangePanel.
     */
    protected void saveDistanceValues(final DoubleRangePanel p) {
        final FormItem[] formItems = p.getFields();

        for (final FormItem item : formItems) {
            saveDistanceValue(p, item);
        }
    }

    /**
     * Validates and stores a value entered in the location mode.
     *
     * @param p
     *            The DoubleArrayPanel.
     * @param item
     *            The item that needs to be validated.
     */
    protected void saveLocationValue(final DoubleArrayPanel p, final FormItem item) {
        if (p.validateForm(item)) {
            setLocationValues(p.getInputValues(item));
        }
    }

    /**
     * Validates and stores value entered in the distance mode.
     *
     * @param p
     *            The DoubleRangePanel.
     * @param item
     *            The item that needs to be validated.
     */
    protected void saveDistanceValue(final DoubleRangePanel p, final FormItem item) {
        if (p.validateForm(item)) {
            setFrom(p.getFrom());
            setTo(p.getTo());
            setStep(p.getStep());
        }
    }

    /**
     * This method creates the panel that contains the checkboxes to switch
     * between the input mode 'location' and 'distance'.
     *
     * @return the checkbox panel.
     */
    protected Canvas createRadioButtonPanel() {
        this.mode = new DynamicForm();

        final RadioGroupItem radio = new RadioGroupItem(FIELD_MODE);
        radio.setShowTitle(false);
        radio.setVertical(false);
        radio.setWrap(false);

        final LinkedHashMap<String, String> values = new LinkedHashMap<String, String>();
        values.put(FIELD_VALUE_LOCATION, this.MESSAGES.location());
        values.put(FIELD_VALUE_DISTANCE, this.MESSAGES.distance());

        final LinkedHashMap<String, String> initial = new LinkedHashMap<String, String>();
        initial.put(FIELD_MODE, FIELD_VALUE_DISTANCE);

        radio.setValueMap(values);
        radio.addChangeHandler(this);

        this.mode.setFields(radio);
        this.mode.setValues(initial);

        return this.mode;
    }

    protected void createDistanceInputPanel() {
        final Config config = Config.getInstance();
        final String url = config.getServerUrl();
        String river = "";

        final ArtifactDescription adescr = this.artifact.getArtifactDescription();
        final DataList[] data = adescr.getOldData();

        if (data != null && data.length > 0) {
            for (final DataList dl : data) {
                if (dl.getState().endsWith("river")) {
                    for (int j = 0; j < dl.size(); j++) {
                        final Data d = dl.get(j);
                        final DataItem[] di = d.getItems();
                        if (di != null && di.length == 1) {
                            river = d.getItems()[0].getStringValue();
                        }
                    }
                }
            }
        }

        this.distanceTable.setDataSource(new DistanceInfoDataSource(url, river, "distances"));
        this.locationsTable.setDataSource(new DistanceInfoDataSource(url, river, "locations"));
    }

    protected double getFrom() {
        return this.from;
    }

    protected void setTo(final String to) {
        try {
            final double toValue = Double.parseDouble(to);
            setTo(toValue);
        }
        catch (final NumberFormatException nfe) {
            // Is there anything to do?
        }
    }

    protected void setFrom(final String from) {
        try {
            final double fromValue = Double.parseDouble(from);
            setFrom(fromValue);
        }
        catch (final NumberFormatException nfe) {
            // Is there anything to do?
        }
    }

    protected void setFrom(final double from) {
        this.from = from;
        /* The doubling should be removed and this.from abolished */
        this.distancePanel.setFrom(from);
    }

    protected double getTo() {
        return this.to;
    }

    protected void setTo(final double to) {
        this.to = to;
        /* The doubling should be removed and this.to abolished */
        this.distancePanel.setTo(to);
    }

    protected double getStep() {
        return this.step;
    }

    protected void setStep(final double step) {
        this.step = step;
    }

    protected double[] getLocationValues() {
        return this.values;
    }

    protected void appendLocation(final String loc) {
        double[] selected;
        if (getLocationValues() != null) {
            final double[] val = getLocationValues();
            selected = new double[val.length + 1];
            for (int i = 0; i < val.length; i++) {
                selected[i] = val[i];
            }
            try {
                selected[val.length] = Double.parseDouble(loc);
            }
            catch (final NumberFormatException nfe) {
                // Is there anything to do here?
            }
        } else {
            selected = new double[1];
            selected[0] = Double.parseDouble(loc);
        }
        setLocationValues(selected);
    }

    protected void setLocationValues(final double[] values) {
        this.values = values;
        this.locationPanel.setValues(values);
    }

    protected void setLocationValues(final String values) {
        final String[] vs = StringArrayParseHelper.getArrayFromRawString(values);
        if (vs == null) {
            return;
        }

        final double[] ds = new double[vs.length];
        int idx = 0;

        for (final String s : vs) {
            try {
                ds[idx++] = Double.valueOf(s);
            }
            catch (final NumberFormatException nfe) {
                // do nothing
            }
        }

        setLocationValues(ds);
    }

    protected void setDistanceValues(final double from, final double to) {
        setFrom(from);
        setTo(to);
        this.distancePanel.setValues(from, to, getStep());
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org