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

com.googlecode.blaisemath.graphics.impl.DelegatingEdgeSetGraphic Maven / Gradle / Ivy

There is a newer version: 3.0.16
Show newest version
package com.googlecode.blaisemath.graphics.impl;

/*
 * #%L
 * BlaiseGraphics
 * --
 * Copyright (C) 2009 - 2020 Elisha Peterson
 * --
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 * 
 * 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.
 * #L%
 */

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toCollection;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.google.common.graph.EndpointPair;
import com.googlecode.blaisemath.annotation.InvokedFromThread;
import com.googlecode.blaisemath.graphics.DelegatingPrimitiveGraphic;
import com.googlecode.blaisemath.graphics.Graphic;
import com.googlecode.blaisemath.graphics.GraphicComposite;
import com.googlecode.blaisemath.graphics.Renderer;
import com.googlecode.blaisemath.style.ObjectStyler;
import com.googlecode.blaisemath.coordinate.CoordinateChangeEvent;
import com.googlecode.blaisemath.coordinate.CoordinateListener;
import com.googlecode.blaisemath.coordinate.CoordinateManager;
import com.googlecode.blaisemath.util.swing.MoreSwingUtilities;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.awt.Shape;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

/**
 * A collection of edges backed by a common set of points.
 * 
 * @param  source object type
 * @param  edge type
 * @param  type of graphics canvas to render to
 * 
 * @author Elisha Peterson
 */
public class DelegatingEdgeSetGraphic,G> extends GraphicComposite {

    private static final Logger LOG = Logger.getLogger(DelegatingEdgeSetGraphic.class.getName());

    public static final String P_EDGE_RENDERER = "edgeRenderer";
    public static final int DEFAULT_MAX_CACHE_SIZE = 5000;

    /** The edges in the graphic. */
    protected final Map> edges = Maps.newHashMap();
    /** Styler for edges */
    protected ObjectStyler edgeStyler = ObjectStyler.create();
    /** Renderer for edges */
    protected Renderer edgeRenderer;
    
    /** Point manager. Maintains objects and their locations, and enables mouse dragging. */
    protected CoordinateManager pointManager;
    /** Listener for changes to coordinates */
    private final CoordinateListener coordListener;
    /** Flag that indicates points are being updated, and no notification events should be sent. */
    protected boolean updating = false;
    /** Queue of updates to be processed */
    private final Queue updateQueue = Queues.newConcurrentLinkedQueue();
    
    /**
     * Initialize with default coordinate manager.
     */
    public DelegatingEdgeSetGraphic() {
        this(CoordinateManager.create(DEFAULT_MAX_CACHE_SIZE), null);
    }
    
    /** 
     * Initialize with given coordinate manager.
     * @param mgr manages source object loc
     * @param edgeRenderer edge renderer
     */
    public DelegatingEdgeSetGraphic(CoordinateManager mgr, Renderer edgeRenderer) {
        coordListener = this::handleCoordinateChange;
        
        setCoordinateManager(mgr);
        setEdgeRenderer(edgeRenderer);
    }

    //region EVENTS
    
    @InvokedFromThread("unknown")
    private void handleCoordinateChange(final CoordinateChangeEvent evt) {
        updateQueue.add(evt);
        MoreSwingUtilities.invokeOnEventDispatchThread(this::processNextCoordinateChangeEvent);
    }
    
    @InvokedFromThread("EDT")
    private void processNextCoordinateChangeEvent() {
        if (!SwingUtilities.isEventDispatchThread()) {
            LOG.log(Level.WARNING, "processNextCoordinateChangeEvent() called from non-EDT");
        }
        CoordinateChangeEvent evt = updateQueue.poll();
        if (evt != null && evt.getSource() == pointManager) {
            updateEdgeGraphics(pointManager.getActiveLocationCopy(), Lists.newArrayList(), true);
        }
    }
    
    @InvokedFromThread("EDT")
    private void clearPendingUpdates() {
        if (!SwingUtilities.isEventDispatchThread()) {
            LOG.log(Level.WARNING, "clearPendingUpdates() called from non-EDT");
        }
        updateQueue.clear();
    }
                
    @InvokedFromThread("EDT")
    private void updateEdgeGraphics(Map locMap, List> removeMe, boolean notify) {
        if (!SwingUtilities.isEventDispatchThread()) {
            LOG.log(Level.WARNING, "updateEdgeGraphics() called from non-EDT");
        }
        updating = true;
        boolean change = false;
        List> addMe = Lists.newArrayList();
        for (E edge : Sets.newLinkedHashSet(edges.keySet())) {
            DelegatingPrimitiveGraphic dsg = edges.get(edge);
            Point2D p1 = locMap.get(edge.nodeU());
            Point2D p2 = locMap.get(edge.nodeV());
            if (p1 == null || p2 == null) {
                if (dsg != null) {
                    removeMe.add(dsg);
                    edges.put(edge, null);
                }
            } else {
                Line2D.Double line = new Line2D.Double(p1, p2);
                if (dsg == null) {
                    dsg = new DelegatingPrimitiveGraphic<>(edge, line,
                        edgeStyler, edgeRenderer);
                    edges.put(edge, dsg);
                    dsg.setObjectStyler(edgeStyler);
                    addMe.add(dsg);
                } else {
                    dsg.setPrimitive(line);
                    change = true;
                }
            }
        }
        change = replaceGraphics(removeMe, addMe) || change;
        updating = false;
        if (change && notify) {
            fireGraphicChanged();
        }
    }

    //endregion

    //region PROPERTIES
    
    public CoordinateManager getCoordinateManager() {
        return pointManager;
    }

    /**
     * Set manager responsible for tracking point locations
     * @param mgr manager
     */
    public final void setCoordinateManager(CoordinateManager mgr) {
        if (this.pointManager != checkNotNull(mgr)) {
            if (this.pointManager != null) {
                this.pointManager.removeCoordinateListener(coordListener);
            }
            this.pointManager = null;
            clearPendingUpdates();
            
            // lock to ensure that no changes are made until after the listener has been setup
            synchronized (mgr) {
                this.pointManager = mgr;
                updateEdgeGraphics(mgr.getActiveLocationCopy(), Lists.newArrayList(), false);
                this.pointManager.addCoordinateListener(coordListener);
            }
            super.graphicChanged(this);
        }
    }

    /**
     * Return map describing graph's edges
     * @return edges
     */
    public Set getEdges() {
        return edges.keySet();
    }

    /**
     * Sets map describing graphs edges. Also updates the set of objects to be
     * the nodes within the edges. Should be called from the EDT.
     * @param newEdges new edges to put
     */
    public final void setEdges(Set newEdges) {
        Set addMe = newEdges.stream().filter(e -> !edges.containsKey(e))
                .collect(toCollection(Sets::newLinkedHashSet));
        Set removeMe = edges.keySet().stream().filter(e -> !newEdges.contains(e))
                .collect(Collectors.toSet());
        if (!removeMe.isEmpty() || !addMe.isEmpty()) {
            List> remove = removeMe.stream().map(edges::remove).collect(Collectors.toList());
            addMe.forEach(e -> edges.put(e, null));
            updateEdgeGraphics(pointManager.getActiveLocationCopy(), remove, true);
        }
    }

    /**
     * Returns the current style styler
     * @return style styler
     */
    public ObjectStyler getEdgeStyler() {
        return edgeStyler;
    }

    /**
     * Sets the current style styler. If null, will use the default style
     * provided by the parent.
     * @param styler used for custom edge styles
     */
    public void setEdgeStyler(ObjectStyler styler) {
        if (this.edgeStyler != styler) {
            this.edgeStyler = styler;
            edges.values().stream().filter(Objects::nonNull).forEach(e -> e.setObjectStyler(styler));
            fireGraphicChanged();
        }
    }

    public @Nullable Renderer getEdgeRenderer() {
        return edgeRenderer;
    }

    public final void setEdgeRenderer(@Nullable Renderer renderer) {
        if (this.edgeRenderer != renderer) {
            Object old = this.edgeRenderer;
            this.edgeRenderer = renderer;
            edges.values().forEach(e -> e.setRenderer(renderer));
            fireGraphicChanged();
            pcs.firePropertyChange(P_EDGE_RENDERER, old, renderer);
        }
    }
    
    //endregion

    @Override
    public void initContextMenu(JPopupMenu menu, Graphic src, Point2D point, Object focus, Set> selection, G canvas) {
        // provide additional info for context menu
        Graphic gfc = graphicAt(point, canvas);
        super.initContextMenu(menu, this, point, 
                gfc instanceof DelegatingPrimitiveGraphic ? ((DelegatingPrimitiveGraphic)gfc).getSourceObject() : focus, 
                selection, canvas);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy