Menu

Script: How to react to when a connector is added to a node?

2020-08-25
2020-09-08
  • Marcus Widerberg

    Script: How to react to when a connector is added to a node?

    Hi!
    I've been searching and googling a bit, have not found a solution.
    I found a relevant INodeChangeListener sample from @boercher here:
    https://sourceforge.net/p/freeplane/discussion/758437/thread/748f90ad/#646c

    I also found a snippet from the freeplane code that looks at the node changed event and does a connector like check, but I cannot see a way to do that from within a script.
    ( https://www.codota.com/web/assistant/code/rs/5c76b4a7df79be0001ca3fb4#L647 )

    Is it possible? To access the node and the target node and the connector itself when a connector has been added?

    I've got just this copied code right now (leaving the adding/removing of the listener out)...

    class ConnectorChangeListener implements INodeChangeListener {
        public void nodeChanged(NodeChangeEvent event) {
            Object property = event.property
            if(NodeLinks.CONNECTOR.equals(property)) { // && event.getNode().getMap().equals(getModel()) 
                                                     // event.getProperty()
                def n = event.node
                println "new connector added to node ${n}."
            }                                                 
            else if (NodeModel.NODE_TEXT.equals(property)) {
                def n = event.node
                println "text of node ${n} has changed from '${event.oldValue}' to '${event.newValue}'."
            }
        }
    }
    

    Thanks for any help or tips. I have not been able to find API docs for the org.freeplane.features.map.NodeChangeEvent and relatives. That might also help.

    BR! /marcus

     
  • lilive

    lilive - 2020-09-08

    Hi,

    I have not been able to find API docs for the org.freeplane.features.map.NodeChangeEvent and relatives.

    In this situation I download the Freeplane sources and dig.

    I've made some tests, here's the code. I did not found the perfect recipe to be sure that a new connector is created. The event is triggered when the node connectors change, this can be a new connector added, a connector removed, or a connector edition. I do not see how to make the difference between them. Depending of what you want to do this can be a problem.

    There is something special with this kind of scripts: the event.node is a org.freeplane.features.map.NodeModel, and not the usual org.freeplane.plugin.script.proxy.Proxy.Node we know in the scripting API. I think maybe it is better to stick to the API rather than using the Freeplane internal classes directly. That's why I've split my code in two part, the first one use the Freeplane internal classes, and the second part use the API. I'm not so sure about how to "convert" a NodeModel to a Proxy.Node, but it seems to work.

    //@ExecutionModes({ON_SELECTED_NODE})
    
    import org.freeplane.features.map.INodeChangeListener
    import org.freeplane.features.map.NodeChangeEvent
    import org.freeplane.features.map.NodeModel
    import org.freeplane.features.link.NodeLinks
    import org.freeplane.features.link.NodeLinkModel
    import org.freeplane.features.mode.Controller
    import org.freeplane.plugin.script.proxy.ScriptUtils
    import org.freeplane.plugin.script.proxy.Proxy;
    
    class ChangeListener implements INodeChangeListener
    {
        public void nodeChanged( NodeChangeEvent event )
        {
            Object property = event.property
    
            if( NodeLinks.CONNECTOR.equals(property) ){
    
                // I read in NodeChangeEvent.java that NodeChangeEvent has
                // two fields oldValue and newValue. I print them:
                // println event.oldValue // -> org.freeplane.features.link.ConnectorModel@172b40e0
                // println event.newValue // -> org.freeplane.features.link.ConnectorModel@172b40e0
                // Both point to the same connector object
    
                NodeModel nm = event.node
                NodeLinkModel connector = event.newValue
    
                println "----------------------------------------------------"
                println "Connectors change event for node ${nm}"
                printInfosWithInternalClasses( nm, connector )
                printInfosWithScriptAPI( nm )
            }
        }
    
        private void printInfosWithInternalClasses( NodeModel node, NodeLinkModel connector )
        {
            println "1 - Using the internal Freeplane classes"
    
            // Get all the connectors from this node
            Collection<NodeLinkModel> connectors = NodeLinks.getLinks( node )
    
            // Does the changed node have the connector source of the event ?
            boolean hasIt = connectors.any{ it == connector }
            println "The node ${hasIt?"":"do not "}have the connector."
    
            // List the connectors
            if( connectors ){
                println "Current connectors from ${node}:"
                connectors.each{
                    println "> One connector to ${it.targetID}"
                }
            } else {
                println "${node} have no connectors."
            }
        }
    
        private void printInfosWithScriptAPI( NodeModel nm )
        {
            println "2 - Using the Freeplane script API"
    
            // Get the Proxy.Node for the NodeModel.
            // Not sure what is the best method. This one... works.
            Proxy.Map map = ScriptUtils.node().map
            Proxy.Node n = map.node( nm.id )
    
            // Now that we have the Proxy.Node we can use the usual scripting
            // API to list the connectors from and to the node
            println "Current ${n} connectors:"
            if( n.connectorsIn || n.connectorsOut ){
                n.connectorsOut.each{
                    if( it.target ) println "> One connector to ${it.target}"
                    else println "> One connector with no target"
                }
                n.connectorsIn.each{
                    if( it.source ) println "> One incoming connector from ${it.source}"
                    else println "> One incoming connector with no source"
                }
            } else {
                println "The node has no connectors."
            }
        }
    }
    
    def mapController = Controller.currentModeController.mapController
    mapController.nodeChangeListeners.findAll {
        it.getClass().name == ChangeListener.class.name
    }.each {
        mapController.removeNodeChangeListener(it)
    }
    mapController.addNodeChangeListener( new ChangeListener() )
    
     
    👍
    1
  • Marcus Widerberg

    Wow - thanks for this thorough reply. Most appreciated. I did go the batch route for this problem, but I may opt to retrace my steps and do the realtime version - which would open up other possibilities. Will try it out later.

    Key to the solution seems to be that the event has .newValue (and .oldValue) which point to the newly created connector. Second step is then to identify this connector, I will try it out to better understand it.

    Thanks again!