view gwt-client/src/main/java/org/dive4elements/river/client/client/ui/LocationDistancePanel.java @ 9175:34dc0163ad2d

DistancePanel Berechnungsstrecke->Darstellungsbereich Refactoring
author gernotbelger
date Mon, 25 Jun 2018 17:58:11 +0200
parents 5e38e2924c07
children 7337034eb5d5
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 = lItems[0].getStringValue().split(" ");
        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 = values.split(" ");

        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