All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.cloudfarming.client.geoviewer.edit.GeoEditorController Maven / Gradle / Ivy

/**
 * Copyright (C) 2010-2012 Agrosense [email protected]
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package nl.cloudfarming.client.geoviewer.edit;

import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
import java.awt.Color;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import nl.cloudfarming.client.geoviewer.Geometrical;
import nl.cloudfarming.client.geoviewer.Palette;
import nl.cloudfarming.client.geoviewer.render.GeoTranslator;
import nl.cloudfarming.client.geoviewer.render.GeometricalWidget;
import nl.cloudfarming.client.geoviewer.render.SimpleGeoTranslator;
import nl.cloudfarming.client.util.ExplorerManagerUtil;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.model.ObjectSceneEvent;
import org.netbeans.api.visual.model.ObjectSceneEventType;
import org.netbeans.api.visual.model.ObjectSceneListener;
import org.netbeans.api.visual.model.ObjectState;
import org.netbeans.api.visual.widget.Widget;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;

/**
 * controller for the surface editor view
 *
 * @author Timon Veenstra
 */
@Messages("geo_editor.action_add_point=Add point")
public class GeoEditorController implements ExplorerManager.Provider, ObjectSceneListener {

    private static final Logger LOGGER = Logger.getLogger("nl.cloudfarming.client.geoviewer.edit.GeoEditorController");
    private static final double FORM_FACTOR = 100000.0;
    private static final int MARGIN_X = 100;
    private static final int MARGIN_Y = 100;
    private final GeoEditorScene scene = new GeoEditorScene();
    private JComponent view = scene.createView();
    private List context;
    private final GeoTranslator geoTranslator;
    private Geometry sceneBoundingBox;
    private ExplorerManager em = new ExplorerManager();
    private Palette palette = new Palette(Color.red);
    private ExplorerSelectionListener esl = new ExplorerSelectionListener();

    public GeoEditorController(List context) {
        geoTranslator = new SimpleGeoTranslator();
        setContext(context);
        em.setRootContext(new RootNode());
        em.addPropertyChangeListener(esl);

        scene.getActions().addAction(ActionFactory.createZoomAction());
        scene.getActions().addAction(ActionFactory.createPanAction());
        scene.getActions().addAction(scene.createObjectHoverAction());
        scene.getActions().addAction(scene.createSelectAction());
        scene.addObjectSceneListener(this, ObjectSceneEventType.OBJECT_STATE_CHANGED, ObjectSceneEventType.OBJECT_SELECTION_CHANGED);

//        scene.getActions().addAction(ActionFactory.createPopupMenuAction(new EditPopupProvider()));
    }

    final void setContext(List context) {
        assert context.size() >= 1 : "context should always at least contain one Geometrical";
        this.context = context;
        applyContext();
    }

    /**
     * Calculates a square bounding box around a list of geometricals containing
     * geometries. The biggest of height and width will be applied to the other
     * variable, creating a square box respecting aspect ratio's when drawn.
     *
     * When there is only one geometrical in the list, and it does not contain a
     * geometry, //TODO figure out what to take as default bounding box
     *
     * @param context List with geometricals
     * @return square bounding box
     */
    public Geometry calculateSquareBoundingBox(List context) {
        Geometry boundingBox = null;
        for (Geometrical geometrical : context) {
            Geometry addedBoundingBox = geometrical.getBoundingBox() != null ? geometrical.getBoundingBox() : geometrical.getGeometry().getEnvelope();
            boundingBox = sceneBoundingBox == null ? addedBoundingBox : sceneBoundingBox.union(addedBoundingBox);
        }
        boundingBox = boundingBox.getEnvelope();

        // gather min and max coordinates
        double minx = 0.0, maxx = 0.0, miny = 0.0, maxy = 0.0;
        for (Coordinate c : boundingBox.getCoordinates()) {
            minx = (c.x < minx) ? c.x : minx;
            miny = (c.y < miny) ? c.y : miny;
            maxx = (c.x > maxx) ? c.x : maxx;
            maxy = (c.y > maxy) ? c.y : maxy;
        }
        // transform to square
        // x axis smaller then y axis, enlarge x
        if (maxx - minx < maxy - miny) {
            maxx = minx + (maxy - miny);
            // y axis smaller then x axis, enlarge y
        } else if (maxx - minx > maxy - miny) {
            maxy = miny + (maxx - minx);
        }

        // create new geometry
        GeometryFactory factory = JTSFactoryFinder.getGeometryFactory(null);
        Coordinate[] coordinates = new Coordinate[5];

        coordinates[0] = new Coordinate(minx, miny);
        coordinates[1] = new Coordinate(minx, maxy);
        coordinates[2] = new Coordinate(maxx, maxy);
        coordinates[3] = new Coordinate(maxx, miny);
        coordinates[4] = new Coordinate(minx, miny);


        CoordinateSequence coordinateSequence = new CoordinateArraySequence(coordinates);
        LinearRing ring = new LinearRing(coordinateSequence, factory);
        Geometry square = factory.createPolygon(ring, null);

        return square;
    }

    private void applyContext() {
        LOGGER.log(Level.FINEST, "applyContext with {0} geometricals", context.size());
        sceneBoundingBox = calculateSquareBoundingBox(context);
        LOGGER.log(Level.FINEST, "Final bounding box {0}", sceneBoundingBox.toText());
    }

    public void activateEditor() {
        GeoEditorModalWindow dialog = new GeoEditorModalWindow(em);
        dialog.setView(view);
        dialog.setVisible(true);
        int returnStatus = dialog.getReturnStatus();
        //TODO process return status
    }

    private void addGeometry(Node geometricalNode) {


        if (!scene.getObjects().contains(geometricalNode)) {
            GeometricalWidget gw = new GeometricalWidget(scene, geoTranslator, geometricalNode.getLookup().lookup(Geometrical.class), palette, sceneBoundingBox);
            gw.getActions().addAction(scene.createSelectAction());
            gw.getActions().addAction(scene.createObjectHoverAction());
            scene.addChild(gw);
            scene.addObject(geometricalNode, gw);
        }

    }

    protected GeoEditorScene getScene() {
        return scene;
    }

//    private void addGeometry(Geometry geometry) {
//        GeoNode prev = null;
//        GeoNode first = null;
//
//        for (int i = 0; i < geometry.getCoordinates().length - 1; i++) {
//            Coordinate c = geometry.getCoordinates()[i];
//            GeoNode geoNode = new GeoNode(new Coordinate(c.x, c.y));
//            if (!scene.getNodes().contains(geoNode)) {
//                Widget w = scene.addNode(geoNode);
//
//                //translate to coordinate to a point
//
//                Point2D location = geoTranslator.geoToPixel(scene.getClientArea(), sceneBoundingBox, geoNode.getCoordinate());//coordinateToPoint(geoNode.getCoordinate());
//                LOGGER.log(Level.FINEST, "Determined local location for the point to be {0}", location.toString());
//                // replace the point based on the widget size
//                int x = (int) (location.getX() - ((double) w.getPreferredSize().width / 2));
//                int y = (int) (location.getY() - ((double) w.getPreferredSize().height / 2));
//                w.setPreferredLocation(new Point(x, y));
//
//                if (first == null) {
//                    first = geoNode;
//                } else {
//                    scene.connectNodes(geoNode, prev);
//
//                }
//                prev = geoNode;
//            }
//
//        }
//        scene.connectNodes(prev, first);
//    }
//    /**
//     * Provides a popup menu for the edit component
//     *
//     */
//    private class EditPopupProvider implements PopupMenuProvider {
//
//        @Override
//        public JPopupMenu getPopupMenu(Widget widget, final Point localLocation) {
//            JPopupMenu menu = new JPopupMenu();
//            menu.add(new AbstractAction(NbBundle.getMessage(this.getClass(), "geo_editor.action_add_point")) {
//
//                @Override
//                public void actionPerformed(ActionEvent e) {
//                    assert SwingUtilities.isEventDispatchThread();
//                    Point p = localLocation;
//                    p.x = p.x - MARGIN_X;
//                    p.y = p.y - MARGIN_Y;
//
//                    GeoNode geoNode = new GeoNode(geoTranslator.pixelToGeo(scene.getClientArea(), sceneBoundingBox, p));
//                    scene.addNode(geoNode).setPreferredLocation((Point) geoTranslator.geoToPixel(scene.getClientArea(), sceneBoundingBox, geoNode.getCoordinate()));
//                    scene.validate();
//                }
//            });
//            return menu;
//        }
//    }
    @Override
    public ExplorerManager getExplorerManager() {
        return em;
    }

    @Override
    public void objectAdded(ObjectSceneEvent event, Object addedObject) {
    }

    @Override
    public void objectRemoved(ObjectSceneEvent event, Object removedObject) {
    }
    //
    // state control variables to prevent update cycles
    private boolean removingObjects = false;
    private boolean incomingSelectionChange = false;
    private boolean outgoingSelectionChange = false;

    @Override
    public void objectStateChanged(ObjectSceneEvent event, Object changedObject, ObjectState previousState, ObjectState newState) {
        LOGGER.finest("objectStateChanged");
        Widget widget = event.getObjectScene().findWidget(changedObject);

        if (widget != null) {
            if (!removingObjects
                    && !incomingSelectionChange) {
                outgoingSelectionChange = true;

                if (changedObject instanceof Node) {
                    //
                    // add node to the selection
                    //                
                    if (!previousState.isSelected() && newState.isSelected()) {
                        ExplorerManagerUtil.addNodeToSelection((Node) changedObject, em);
                    }
                    //
                    // remove node from the selection 
                    //
                    if (previousState.isSelected() && !newState.isSelected()) {
                        ExplorerManagerUtil.removeNodeFromSelection((Node) changedObject, em);
                    }
                }
                outgoingSelectionChange = false;
            }
        }
    }

    @Override
    public void selectionChanged(ObjectSceneEvent event, Set previousSelection, Set newSelection) {
    }

    @Override
    public void highlightingChanged(ObjectSceneEvent event, Set previousHighlighting, Set newHighlighting) {
    }

    @Override
    public void hoverChanged(ObjectSceneEvent event, Object previousHoveredObject, Object newHoveredObject) {
    }

    @Override
    public void focusChanged(ObjectSceneEvent event, Object previousFocusedObject, Object newFocusedObject) {
    }

    private class RootNode extends AbstractNode {

        public RootNode() {
            super(Children.create(new GeometricalChildFactory(), false));
        }
    }

    private class GeometricalChildFactory extends ChildFactory {

        @Override
        protected boolean createKeys(List toPopulate) {
            toPopulate.addAll(context);
            return true;
        }

        @Override
        protected Node createNodeForKey(Geometrical key) {
            try {
                // create a new geometrical node
                GeometricalNode node = new GeometricalNode(key);
                // add the geometry to the editor window
                addGeometry(node);
                return node;
            } catch (IntrospectionException ex) {
                LOGGER.log(Level.SEVERE, "Error occured while creating a node for {0} : {0}", new Object[]{key, ex});
                return null;
            }
        }
    }

    /**
     * Listener for changes in the node selection of the explorer. Will change
     * the selection state of the corresponding widgets
     */
    private class ExplorerSelectionListener implements PropertyChangeListener {

        public ExplorerSelectionListener() {
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            //
            // synchronize selection changes in the explorer manager to the editor
            //
            if (!outgoingSelectionChange && ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) {
                Node[] newSelection = (Node[]) evt.getNewValue();
                Set selection = new HashSet<>();

                for (Node selected : newSelection) {
                    // only add to selection if the object is part of this scene
                    if (scene.findStoredObject(selected) != null) {
                        selection.add(selected);
                    }
                }

                incomingSelectionChange = true;
                scene.setSelectedObjects(selection);
                incomingSelectionChange = false;
                view.repaint();
            }
        }
    }
}