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

eu.limetri.client.mapviewer.nb.jxmap.LayerPanel Maven / Gradle / Ivy

There is a newer version: 1.4.4
Show newest version
/**
 * Copyright (C) 2008-2012 AgroSense Foundation.
 *
 * AgroSense is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * There are special exceptions to the terms and conditions of the GPLv3 as it is applied to
 * this software, see the FLOSS License Exception
 * .
 *
 * AgroSense is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with AgroSense.  If not, see .
 */
package eu.limetri.client.mapviewer.nb.jxmap;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
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 javax.swing.SwingUtilities;


import org.jdesktop.swingx.JXPanel;
import org.netbeans.api.visual.action.WidgetAction.WidgetMouseEvent;
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.LayerWidget;
import org.netbeans.api.visual.widget.Widget;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node;
import org.openide.nodes.NodeEvent;
import org.openide.nodes.NodeListener;
import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent;

import eu.limetri.client.mapviewer.api.Layer;
import eu.limetri.client.mapviewer.api.LayerInfo;
import eu.limetri.client.mapviewer.api.MapOnlyNodeProvider;
import eu.limetri.client.mapviewer.api.Palette;
import eu.limetri.client.mapviewer.api.SingleObjectLayer;
import eu.limetri.client.mapviewer.api.DynamicPoint;
import eu.limetri.client.mapviewer.api.util.ExplorerManagerUtil;
import eu.limetri.client.mapviewer.swing.JXMapViewer;
import eu.limetri.client.mapviewer.swing.jxmap.layerlist.LayerListPanel;
import eu.limetri.client.mapviewer.swing.jxmap.map.MapMouseEvent;
import eu.limetri.client.mapviewer.swing.jxmap.map.MapScene;
import eu.limetri.client.mapviewer.swing.jxmap.map.MapWidgetFactory;
import eu.limetri.client.mapviewer.swing.jxmap.map.PickLocationAction;
import eu.limetri.client.mapviewer.swing.render.GeometricalWidget;
import java.util.Collection;

/**
 * Layer panel containing objects object layers will be painted as images with a
 * backgroundPainter by default when the layer is activated, the actual objects
 * (widgets) will be painted over the background.
 *
 * @author Timon Veenstra
 */
public class LayerPanel extends JXPanel implements ObjectSceneListener, MapMouseListener, PropertyChangeListener {

    private static final Logger LOGGER = Logger.getLogger(LayerPanel.class.getCanonicalName());
    private final MapScene scene;
    private final JComponent view;
    private boolean removingObjects = false;
    private final Layer layer;
    private final LayerInfo info;
    private final JXMapViewer mapViewer; // needed to create widgets which need to be aware of the map
    private final int id;
    private final RootMapPanel.LayerGroup group = RootMapPanel.LayerGroup.LAYER_ACTIVE;
    private boolean outgoingSelectionChange = false;
    private boolean incomingSelectionChange = false;
    //
    // widget layer
    private final LayerWidget mainLayer;
    private final LayerWidget iconLayer;
    
    private Node node;

    public LayerPanel(final Node node, JXMapViewer mapViewer, int id, LayerInfo layerInfo) {
        //
        // initalize the scene, view and main layer which map objects are drawn on
        //
        this.node = node;
        this.scene = new MapScene(mapViewer);
        this.view = scene.createView();
        this.mainLayer = new LayerWidget(scene);
        this.iconLayer = new LayerWidget(scene);
        //TODO add connection layer for connection widgets

        this.layer = node.getLookup().lookup(Layer.class);
        assert this.layer != null;

        this.id = id;
        this.info = layerInfo;

        this.mapViewer = mapViewer;


        setLayout(new BorderLayout());
        this.scene.setOpaque(false);
        this.scene.addChild(this.mainLayer);
        this.scene.addChild(this.iconLayer);
        this.scene.setMaximumBounds(new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE));

        LOGGER.finest("Adding the node");
        addObject(node);

        this.scene.addObjectSceneListener(this, ObjectSceneEventType.OBJECT_STATE_CHANGED, ObjectSceneEventType.OBJECT_SELECTION_CHANGED);

        node.addNodeListener(new NodeListener() {
            @Override
            public void childrenAdded(NodeMemberEvent ev) {
                final Node[] addedObjects = new Node[ev.getDelta().length];
                int i = 0;
                for (Node node : ev.getDelta()) {
                    addedObjects[i] = node;
                    i++;
                }
                addObjects(addedObjects);
            }

            @Override
            public void childrenRemoved(NodeMemberEvent ev) {
                final List removeObjects = new ArrayList<>();
                removeObjects.addAll(Arrays.asList(ev.getDelta()));
                removeObjects(removeObjects);
            }

            @Override
            public void childrenReordered(NodeReorderEvent ev) {
            }

            @Override
            public void nodeDestroyed(NodeEvent ev) {
            }

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
            }
        });


        setOpaque(false);

        this.info.addPropertyChangeListener(LayerInfo.PROPERTY_VISIBLE, new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                setVisible((Boolean) evt.getNewValue());
                repaint();
            }
        });


        this.info.addPropertyChangeListener(LayerInfo.PROPERTY_TRANSPARENCY, new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                setAlpha(info.getTransparency());
                repaint();
            }
        });

        this.info.addPropertyChangeListener(LayerInfo.PROPERTY_SHOW_ICONS, new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                updateIcons();
                repaint();
            }
        });

//        if (node.getChildren() == null || node.getChildren().getNodesCount() == 0) {
//            LOGGER.warning("Empty ObjectLayer: The LayerNode did not contain any children.");
//        } else {
//            addObjects(node.getChildren().getNodes());
//        }


        // listen for geometry creation, re-add node to create widgets:
        if (layer instanceof SingleObjectLayer) {
            layer.addPropertyChangeListener(SingleObjectLayer.PROP_GEOMETRY, new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (evt.getOldValue() == null && evt.getNewValue() != null) {
                        addObjects(new Node[]{node});
                    }
                }
            });
        }

        updateIcons();
        add(view);
        setActive(true);
    }

    private void updateIcons() {
        iconLayer.setVisible(info.showIcons());
    }

    public static boolean isRightMouseButton(WidgetMouseEvent anEvent) {
        return ((anEvent.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK);
    }

    /**
     * remove objects if remove is called from the event dispatch thread it ill
     * be executed immediate, if not it will be scheduled on the dispatch
     * thread.
     *
     * @param objects
     */
    private void removeObjects(final List objects) {
        if (EventQueue.isDispatchThread()) {
            internalRemoveObjects(objects);
        } else {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    internalRemoveObjects(objects);
                }
            });
        }
    }

    private void internalRemoveObjects(List objects) {
        assert objects != null;
        removingObjects = true;
        for (Node node : objects) {
            List widgets = this.scene.findWidgets(node);
            if (widgets != null) {
                for (Widget w : widgets) {
                    mainLayer.removeChild(w);
                }
                this.scene.removeObject(node);
            }

        }
        this.scene.validate();
        removingObjects = false;
    }

    /**
     * add objects if called from the event dispatch thread it ill be executed
     * immediate, if not it will be scheduled on the dispatch thread.
     *
     * @param objects
     */
    private void addObjects(final Node[] nodes) {
        if (SwingUtilities.isEventDispatchThread()) {
            internalAddObjects(nodes);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    internalAddObjects(nodes);
                }
            });
        }
    }

    /**
     * Direct add
     *
     * @param nodes
     */
    private void internalAddObjects(final Node[] nodes) {
        assert SwingUtilities.isEventDispatchThread();

        for (Node node : nodes) {
            addObject(node);
        }
        scene.validate();
    }

    /**
     * Add an object to the panel. this method will add the object to the scene
     * and create a widget attached to it.
     *
     * @param objectNode
     */
    private void addObject(final Node node) {
        assert SwingUtilities.isEventDispatchThread();

        addMapActions(node);
        Widget widget = MapWidgetFactory.createWidgetFromNode(node, scene);
        if (widget != null) {
            this.mainLayer.addChild(widget);
            initPaletteChangeListener(node);

            Widget iconWidget = MapWidgetFactory.createIconWidgetFromNode(node, scene);
            //TODO create a connector between the icon and the geographical widget, see https://blogs.oracle.com/geertjan/entry/acceptprovider_connectprovider
            //TODO icon and label should be in different widget
            if (iconWidget != null) {
                this.iconLayer.addChild(iconWidget);
                this.scene.addObject(node, new Widget[]{widget, iconWidget});
            } else {
                this.scene.addObject(node, widget);
            }
        }

        addMapOnlyChildNodes(node);
        if (node.getChildren() != null && node.getChildren().getNodes().length > 0) {
            LOGGER.finest("Node contains children, adding them recursively");
            for (Node child : node.getChildren().getNodes()) {
                addObject(child);
            }
        }
    }

    /**
     * Checks if the given Node contains map only children and adds them to the
     * panel.
     *
     * @param node The node (with or without a {@link MapOnlyNodeProvider} in
     * its Lookup
     */
    private void addMapOnlyChildNodes(Node node) {
        MapOnlyNodeProvider parent = node.getLookup().lookup(MapOnlyNodeProvider.class);
        if (parent != null) {
            internalAddObjects(parent.getMapOnlyNodes());
        }
    }

    /**
     * Adds some map actions to the DynamicGeometrical in the lookup. These map
     * actions can only be performed in the context of a map. Doesn't do
     * anything if there is no DynamicGeographical in the lookup.
     *
     * @param node The node (with a DynamicGeometrical in its Lookup)
     */
    private void addMapActions(Node node) {
        // FIXME: actions are duplicated when the node is dragged to the map a second time (depends on collection in dyn.geom.impl.)
        // FIXME: picklocation action keeps a reference to the mapviewer after the node is removed from map.
        DynamicPoint point = node.getLookup().lookup(DynamicPoint.class);
        if (point != null) {
            point.addMapAction(PickLocationAction.create(node, scene));
        }
    }

    /**
     * activate or deactivate this layer when layer gets activated, the view
     * will be made visible, painting all containing objects as widgets and
     * providing interaction.
     *
     * @param active
     */
    final void setActive(boolean active) {
        //TODO since we no longer make distinction to active and passive layers we don't want to 
        // disable background painting.
        // We might want to prevent doulbe painting of objects though..
//        this.view.setVisible(active);
//        setBackgroundPainter(active ? null : getPainter());
//        this.view.repaint();
    }

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

    @Override
    public void objectRemoved(ObjectSceneEvent event, Object removedObject) {
    }

    @Override
    public void objectStateChanged(ObjectSceneEvent event, Object changedObject, ObjectState previousState, ObjectState newState) {
//        Widget widget = event.getObjectScene().findWidget(changedObject);
//        
//        LOGGER.log(Level.FINEST, "objectStateChanged for {0}", changedObject);
//
//        if (widget != null) {
//            if (!removingObjects
//                    && !incomingSelectionChange
//                    && changedObject instanceof Node) {
//                Node node = (Node) changedObject;
//
//                widget.setForeground(getLayer().getPalette().getColorForState(newState));
//
//                outgoingSelectionChange = true;
//                ExplorerManager em = ExplorerManager.find(this);
//                //
//                // add node to the selection
//                //
//                if (!previousState.isSelected() && newState.isSelected()) {
//                    ExplorerManagerUtil.setSelectedObject(changedObject, em);
//                }
//                outgoingSelectionChange = false;
//            }
//        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        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();
        } else if (Layer.PROP_PALETTE.equals(evt.getPropertyName())) {
            view.repaint();
        }
    }

    @Override
    public void selectionChanged(ObjectSceneEvent event, Set previousSelection, Set newSelection) {
        //if selected, set layerInfo as selected
        if (newSelection.size() > 0) {
            Node obj = (Node) newSelection.toArray()[0]; //first selected node
            boolean equals = LayerListPanel.compareNodes(obj, node, "nl.cloudfarming.client.field.model.Field");
            if (equals) {
                info.setSelected(true);
            } else {
                info.setSelected(false);
            }
        }
        
        LOGGER.log(Level.FINEST, "selectionChanged for previous selected: {0}, newly selected {1}", new Object[]{previousSelection.size(), newSelection.size()});
        if (!removingObjects
                && !incomingSelectionChange) {

            outgoingSelectionChange = true;
            ExplorerManager em = ExplorerManager.find(this);
            ExplorerManagerUtil.setSelectedObjects(newSelection.toArray(new Object[newSelection.size()]), em);
        }
        outgoingSelectionChange = false;
//        LOGGER.log(Level.FINEST, "selectionChanged for previous selected: {0}, newly selected {1}", new Object[]{previousSelection.size(), newSelection.size()});
//        for (Object o : previousSelection) {
//            Widget widget = event.getObjectScene().findWidget(o);
//            if (widget != null) {
//                if (o instanceof Node) {
//                    widget.setForeground(getLayer().getPalette().getColorForState(widget.getState()));
//                     //Inform implementations of SelectDeselectHandler of state change
//                    SelectDeselectHandler.Event.triggerDeselect((Node)o);
//                }
//            }
//        }
//
//        for (Object o : newSelection) {
//            Widget widget = event.getObjectScene().findWidget(o);
//            if (widget != null) {
//                if (o instanceof Node) {
//                    widget.setForeground(getLayer().getPalette().getColorForState(widget.getState()));
//                     //Inform implementations of SelectDeselectHandler of state change 
//                    SelectDeselectHandler.Event.triggerSelect((Node)o);
//                }
//            }
//        }
    }

    @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) {
    }

    @Override
    public void onMapMouseEvent(MapMouseEvent mapMouseEvent) {
        if (info.isVisible()) {
            MouseEvent event = mapMouseEvent.getEvent();
            view.dispatchEvent(event);
            if (event.isPopupTrigger() && isLayerObjectHit(event.getPoint(), scene)) {
                mapMouseEvent.consume();
            }
        }
    }

    @Override
    public void mapMousePressed(MapMouseEvent mapMouseEvent) {
    }

    @Override
    public void mapMouseReleased(MapMouseEvent mapMouseEvent) {
    }

    @Override
    public void mapMouseDragged(MapMouseEvent mapMouseEvent) {
    }

    /**
     * Recursive method checking if a layerObject is clicked
     */
    private boolean isLayerObjectHit(final Point point, final Widget widget) {
        if ((widget instanceof GeometricalWidget) && widget.isHitAt(widget.convertSceneToLocal(point))) {
            return true;
        }

        for (final Widget child : widget.getChildren()) {
            if (isLayerObjectHit(point, child)) {
                return true;
            }
        }

        return false;
    }

    public JXMapViewer getMapViewer() {
        return mapViewer;
    }

    public LayerInfo getInfo() {
        return info;
    }

    public Layer getLayer() {
        return layer;
    }

    @Override
    public RootMapPanel.LayerGroup getGroup() {
        return group;
    }

    @Override
    public int getId() {
        return id;


    }

    private void initPaletteChangeListener(Node node) {
        Layer l = node.getLookup().lookup(Layer.class);
        l.addPropertyChangeListener(
                new PaletteChangeListener(node));
    }

    /**
     * This PropertyChangeListener updates the foreground color for the widgets
     * that belong to the given node if the palette of its layer changes.
     */
    private class PaletteChangeListener implements PropertyChangeListener {

        private Node node;

        public PaletteChangeListener(Node n) {
            this.node = n;
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            LOGGER.log(Level.FINEST, "Received a PropertyChangeEvent for property {0}", evt.getPropertyName());
            if (evt.getPropertyName().equals(Layer.PROP_PALETTE)) {
                Palette palette = ((Palette) evt.getNewValue());
                LOGGER.log(Level.FINEST, "Updating {0}'s palette to {1}", new String[]{node.getName(), palette.toString()});
                List widgets = scene.findWidgets(node);
                for (Widget widget : widgets) {
                    Color color = ((Palette) evt.getNewValue()).getColorForState(widget.getState());
                    widget.setForeground(color);
                }
                scene.validate();
            }
        }
    }
}