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

nl.cloudfarming.client.geoviewer.jxmap.map.LayerPanel Maven / Gradle / Ivy

Go to download

AgroSense geoviewer JXMap implementation. Contains a map/geoviewer TopComponent based on the JXMap classes from swingx.

There is a newer version: 13.03-beta
Show newest version
/**
 * 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.jxmap.map;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
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.Logger;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import nl.cloudfarming.client.geoviewer.Geographical;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.LayerInfo;
import nl.cloudfarming.client.geoviewer.jxmap.render.JXMapGeoTranslator;
import nl.cloudfarming.client.geoviewer.render.GeoTranslator;
import nl.cloudfarming.client.geoviewer.render.GeometricalWidget;
import nl.cloudfarming.client.geoviewer.render.IconWidget;
import nl.cloudfarming.client.util.ExplorerManagerUtil;
import org.jdesktop.swingx.JXMapViewer;
import org.jdesktop.swingx.JXPanel;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.PopupMenuProvider;
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 org.openide.util.Utilities;

/**
 * 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("nl.cloudfarming.client.geoviewer.jxmap.map.LayerPanel");
    private final MapScene scene;
    private final JComponent view;
    private boolean removingObjects = false;
    private final Layer layer;
    private final LayerPainter painter;
    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;

    public LayerPanel(Node node, JXMapViewer mapViewer, int id, LayerInfo layerInfo) {
        //
        // initalize the scene, view and main layer which map objects are drawn on
        //
        this.scene = new MapScene(mapViewer);
        this.view = scene.createView();
        this.mainLayer = 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.painter = new LayerPainter(node);
        //turned off deprecated layer painter
//        setBackgroundPainter(this.painter);
        this.info = layerInfo;

        this.mapViewer = mapViewer;


        setLayout(new BorderLayout());
        this.scene.setOpaque(false);
        this.scene.addChild(this.mainLayer);
        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();
            }
        });

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

        add(view);
        setActive(true);
    }

    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();

        Widget widget = MapWidgetFactory.createWidgetFromNode(node, scene);
        if (widget != null) {
            addActions(widget, node);
        }

        Widget iconWidget = createIconWidget(node);
        //TODO add same actions to the icon widget
        //TODO create a connector between the icon and the geographical widget

        if (widget != null) {
            this.mainLayer.addChild(widget);

            if (iconWidget != null) {
                this.mainLayer.addChild(iconWidget);
                this.scene.addObject(node, new Widget[]{widget, iconWidget});
            } else {
                this.scene.addObject(node, widget);
            }

            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);
                }
            }
        }

    }

    /**
     * Tries to create and returns an icon widget for the given node. No widget
     * is created if the node's lookup doesn't contain a geographical or the
     * geographical doesn't have an icon.
     *
     * @param The node (with a geographical in its lookup)
     * @return The created icon widget or null if none created.
     */
    private Widget createIconWidget(Node node) {
        Geographical geographical = node.getLookup().lookup(Geographical.class);
        GeoTranslator geoTranslator = new JXMapGeoTranslator(scene.getMapViewer());
        Widget iconWidget = null;
        if (geographical != null && geographical.getIconPath() != null) {
            iconWidget = new IconWidget(geographical, layer.getPalette(), geoTranslator, scene);
        }
        return iconWidget;
    }

    private void addActions(Widget widget, final Node node) {
        widget.getActions().addAction(scene.createObjectHoverAction());
        widget.getActions().addAction(scene.createWidgetHoverAction());
        widget.getActions().addAction(scene.createSelectAction());

        widget.getActions().addAction(ActionFactory.createPopupMenuAction(new PopupMenuProvider() {

            @Override
            public JPopupMenu getPopupMenu(Widget widget, Point localLocation) {
                JPopupMenu menu = Utilities.actionsToPopup(node.getActions(true), node.getLookup());
                return menu;
            }
        }));
    }

    /**
     * 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);

        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.addObjectToSelection(changedObject, em);
                }
                //
                // remove node from the selection if it is not removed
                // and return to normal color
                //
                if (previousState.isSelected() && !newState.isSelected()) {
                    ExplorerManagerUtil.removeObjectFromSelection(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) {
        for (Object o : previousSelection) {
            Widget widget = event.getObjectScene().findWidget(o);
            if (widget != null) {
                if (o instanceof Node) {
                    widget.setForeground(getLayer().getPalette().getColorForState(widget.getState()));
                }
            }
        }

        for (Object o : newSelection) {
            Widget widget = event.getObjectScene().findWidget(o);
            if (widget != null) {
                if (o instanceof Node) {
                    widget.setForeground(getLayer().getPalette().getColorForState(widget.getState()));
                }
            }
        }
    }

    @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) {
        view.dispatchEvent(mapMouseEvent.getEvent());
        if (mapMouseEvent.getEvent().isPopupTrigger() && isLayerObjectHit(mapMouseEvent.getEvent().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;
    }

    public LayerPainter getPainter() {
        return painter;
    }

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

    @Override
    public int getId() {
        return id;
    }
}