Scripting/Mind map documentation

From FreeMind

Revision as of 12:16, 30 August 2010 by Dan Polansky (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The following is a documentation for scripting from the help mind map supplied with FreeMind.

Contents

Scripting Support

FreeMind can now be scripted by using Groovy scripts. Groovy is a very easy to use scripting language best integrating into FreeMind.

There are two possibilities to use scripts:

  • Choose the script editor to easily add, change and test your scripts. Technically, scripts are attached to a node via an attribute starting with "script" (like "script1") that contains the script. Every time you choose "Evaluate" from the tools menu, all scripts attached to nodes are executed.
  • Create or change a pattern and press the script button. The script editor appears and your script will be associated to a pattern. Every time you apply the pattern to some nodes, the script is executed for that nodes automatically. Thus, you can have the scripts with keyboard shortcuts as the patterns are accessible via shortcuts.

Every script is at evaluation time started with two predefined java objects coming from the map:

  • node is the current node. It is a freemind.modes.MindMapNode. This node can be used to retrieve information about the contents, its children or its formatting. Don't use the setter to change the node. Use the following instead:
  • c is the current controller. It is a freemind.modes.mindmapmode.MindMapController. This controller should be used to change information. For example, if you want to change the nodes text or if you want to add children. The methods that can be used are sumarized in freemind.modes.mindmapmode.actions.MindMapActions.

There are two automatisms regarding the effect of a script:

  • If a script starts with "=", the result of the script is taken to be the new nodes text. Example script1: =17+4. If executed, the node the script is associated to will be changed to 21.
  • If a script starts with letters (digits and '_') only and then a "=" sign, like "sum=17+4", the result is taken to be a (possibly new) attribute named "sum" in this case with the content 21.

For more examples consult our little scripting guide below or our web pages.

A last word on security: before scripts is evaluated for the first time in FreeMind, the user is asked whether or not he allows it. The answer can be stored for every script but observe that a malicious script is able to perform every action on your computer that your users rights allow up to delete all files or send them to pirates.ru. This said, be careful and don't allow scripts when you don't know that the author is trusted. Finally, scripts are never evaluated automatically in FreeMind for these reasons. Thus, you can open a map without problems and have a look at the scripts it contains.

Little FreeMind Scripting Guide

If your scripts want to change some map data (which they commonly want to) they should rely on the methods provided by the MindMapController c.

These methods are summarized and partially documented in the class "MindMapActions":

http://freemind.cvs.sourceforge.net/freemind/freemind/freemind/modes/mindmapmode/actions/MindMapActions.java?view=log&pathrev=fm_060405_integration

The listeners mentioned below (ie. NodeSelectionListener and NodeLifetimeListener) can be registered and found in the class "ModeController" (also c):

http://freemind.cvs.sourceforge.net/freemind/freemind/freemind/modes/ModeController.java?view=log&pathrev=fm_060405_integration

Here we present some snippets of useful Groovy code that can be used as parts of your scripts. More scripts can be found on our web sites.

  • Change the node text:
    =17+4
    (Explanation: if a script starts with "=", the result of the script is taken as the new nodes text.)
  • Change an attributes value:
    attribute_name=17+4
    (Explanation: if a script starts with a name and then directly a "=" sign, its result is associated to this attribute which is created if not already present.)
  • Read and change the nodes text:
    c.setNodeText(node, node.getText() + "_CHANGED");
  • Read an attribute
    def value = node.getAttribute("key"); // value is of type String.
  • Create or change attributes: the following method checks whether or not an attribute with the same key exists and replaces it. Otherwise a new attribute is created and added.
    c.editAttribute(node, "key", "new value");
  • Remove an attribute by name:
    c.editAttribute(node, "key", null);
    This method returns the former index of the attribute, or -1 if the key wasn't found.
  • Traverse all children
    def it = node.childrenUnfolded();
    while(it.hasNext()) {
    def child = it.next();
    }
  • Traverse all children and its children recursively. The following examples prints the content of every node including its childs

def stack = new java.util.Stack();
stack.push(node);
while(stack.size()>0) 
  {
    def current =stack.pop();
    print current.getShortText(c) + ", ";
    stack.addAll(current.getChildren());
  }
  • Real world example: nodes may have an attribute "work" that specifies the work needed for the specific work package (e.g. in days). This script computes the sum of all work packages such that each node gets an attribute "sum" containing the amount of work needed for all descendants. This script, if executed via Alt+F8, automatically applies to the root of the map. But, every time, you change the values, you have to reexecute this script.

def calcWork(child) {
  def sum = 0;
  def it = child.childrenUnfolded(); 
  while(it.hasNext()) { 
    def child2 = it.next(); 
    sum += calcWork(child2);
    def w = child2.getAttribute("work");
    if(w != null)
      sum += Integer.parseInt( w);
  }
  if(sum>0)
    c.editAttribute(child, "sum", (String) sum);
  return sum;
}
 
calcWork(c.getRootNode());
  • A very advanced example: the last script is integrated into a listener that detects node changes, so the sums are always recreated when a node is changed. This script introduces a new element in scripting: the "cookies". It is a usual HashMap where scripts can store values that they need the next time, they are executed. For every map, there is a new cookie map, such that cookies are map local. Moreover, they are not stored persistently and are lost after termination of FreeMind or after closing a map. In this example, they serve as a static variable in which it is stored whether or not the script was already executed and which listener was used in order to deregister the old one first.

class MyNodeListener implements freemind.modes.ModeController.NodeSelectionListener {
  freemind.modes.mindmapmode.MindMapController c;
  MyNodeListener(freemind.modes.mindmapmode.MindMapController con) {
    this.c = con;
  }
 
  /** 
   * Sent, if a node is changed
   * */
  void onUpdateNodeHook(freemind.modes.MindMapNode node){		
    calcWork(c.getRootNode());
  };
 
  /** Is sent when a node is selected.
   */
  void onSelectHook(freemind.view.mindmapview.NodeView node){};
  /**
   * Is sent when a node is deselected.
   */
  void onDeselectHook(freemind.view.mindmapview.NodeView node){};
 
  /**
   * Is issued before a node is saved (eg. to save its notes, too, even if the notes is currently edited).
   */
  void onSaveNode(freemind.modes.MindMapNode node){};
 
  def calcWork(child) {
    def sum = 0;
    def it = child.childrenUnfolded(); 
    while(it.hasNext()) { 
      def child2 = it.next(); 
      sum += calcWork(child2);
      def w = child2.getAttribute("work");
      if(w != null)
        sum += Integer.parseInt( w);
    }
    if(sum>0)
      c.editAttribute(child, "sum", (String) sum);
    return sum;
  }
}
 
def cookieKey = "work_update_listener";
if(cookies.get(cookieKey) != null) {
  c.deregisterNodeSelectionListener(cookies.get(cookieKey));
}
def newListener = new MyNodeListener(c);
cookies.put(cookieKey, newListener);
c.registerNodeSelectionListener(newListener);
  • A sorting example: Currently we provide a function that sorts all children by name, but if you want to sort them by their icons for example, you can use the following script (or change it, if you have different sorting criteria):

import java.awt.datatransfer.Transferable;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Vector;
import freemind.modes.MindMapNode;
 
class IconComparator implements java.util.Comparator {
  int compare(java.lang.Object pArg0, java.lang.Object pArg1) {
    if (pArg0 instanceof MindMapNode) {
      MindMapNode node1 = (MindMapNode) pArg0;
      if (pArg1 instanceof MindMapNode) {
        MindMapNode node2 = (MindMapNode) pArg1;
        String iconText1 = getIconText(node1);
        String iconText2 = getIconText(node2);
        //print "comparing" + iconText1 + " with " + iconText2 + "\n";
        return iconText1.compareToIgnoreCase(iconText2);
      }
    }
    return 0;
  }
  def getIconText(MindMapNode n) {
    if(n.getIcons() == null || n.getIcons().size()==0) 
      return "";
    def retString = "";
    def it = n.getIcons().iterator();
    while(it.hasNext()) {
      retString +=it.next().getName()+", ";
    }
    return retString;
  }
}
 
 
// we want to sort the children of the node:
Vector children = new Vector();
// put in all children of the node
children.addAll(node.getChildren());
// sort them
java.util.Collections.sort(children, new IconComparator());
//print "The set has " + children.size() + " entries\n";
// now, as it is sorted. we cut the children
def it2 = children.iterator();
while (it2.hasNext()) {
  MindMapNode child = (MindMapNode) it2.next();
  Vector childList = new Vector();
  childList.add(child);
  Transferable cut = c.cut(childList);
  // paste directly again causes that the node is added as the last one.
  c.paste(cut, node);
}
c.select(c.getNodeView(node));
  • A presentation script. Everytime you select a node, all other nodes are closed and this node is expanded by one. Just give it a try.

class MyNodeListener implements freemind.modes.ModeController.NodeSelectionListener {
  freemind.modes.mindmapmode.MindMapController c;
  MyNodeListener(freemind.modes.mindmapmode.MindMapController con) {
    this.c = con;
  }
  /** 
   * Sent, if a node is changed
   */
  void onUpdateNodeHook(freemind.modes.MindMapNode node){		
  };
 
  /** Is sent when a node is selected.
   */
  void onSelectHook(freemind.view.mindmapview.NodeView node){
    if(c.getSelecteds().size()>1)
      return;
    // unfold node:
    c.setFolded(node.getModel(), false);
    // fold every child:
    def it2 = node.getModel().childrenUnfolded().iterator();
    while (it2.hasNext()) {
      def child = it2.next();
      c.setFolded(child, true);
    }
    // close everything else:
    foldEverybody(node.getModel().getParent(),node.getModel());
  };
 
  /** Is sent when a node is deselected.
   */
  void onDeselectHook(freemind.view.mindmapview.NodeView node){};
 
  /**
   * Is issued before a node is saved (eg. to save its notes, too, even if the notes is currently edited).
   */
  void onSaveNode(freemind.modes.MindMapNode node){};
  def foldEverybody(child, exception) {
    if(child == null || child.isRoot())
      return;
    def it = child.childrenUnfolded();
    while(it.hasNext()) {
      def child2 = it.next();
      if(child2 != exception) {
        c.setFolded(child2, true);
      }
    }
    if(!child.getParent().isRoot())
      foldEverybody(child.getParent(), exception.getParent());
  } 
}
 
def cookieKey = "presentation_listener";
if(cookies.get(cookieKey) != null) {
  c.deregisterNodeSelectionListener(cookies.get(cookieKey));
}
def newListener = new MyNodeListener(c);
cookies.put(cookieKey, newListener);
c.registerNodeSelectionListener(newListener);

How to install a script as a menu item

Once, you've created or found some interesting scripts, you probably want to get a FreeMind menu item with an own shortcut to execute the script.

To do this, save the script to a file and edit "ScriptingEngine.xml" inside the FreeMind script directory inside your installation.

You'll find a template for a script action that is commented out (ie. surrounded by <!-- ... -->). Uncomment the template and fill out the following bold places:

 <plugin_action
   name="GroovyGroovy"
   documentation="this is my first installed groovy script."
   label="plugins/GroovyScript1"
   base="freemind.extensions.ModeControllerHookAdapter"
   class_name="plugins.script.ScriptingEngine">
   <plugin_mode class_name="freemind.modes.mindmapmode"/>
   <plugin_menu location="menu_bar/extras/first/scripting/groovy1"/>
   <plugin_property name="ScriptLocation" value="/home/foltin/test.groovy"/>
 </plugin_action>

The most important change is the location of the script. Moreover, if you have several scripts you want to install, the labels and the menu_location must be unique.

If you now restart FreeMind you get a new menu item (in this example in the "Extras" menu) that carries out your script. Observe, that the "node" variable points to the root node.

If you want to have a keyboard short cut for the new script, you have to add the bold line into the entry in ScriptingEngine.xml like:

 <plugin_action
   name="GroovyGroovy"
   documentation="this is my first installed groovy script."
   label="plugins/GroovyScript1"
   key_stroke="control shift M" 
   base="freemind.extensions.ModeControllerHookAdapter"
   class_name="plugins.script.ScriptingEngine">
   <plugin_mode class_name="freemind.modes.mindmapmode"/>
   <plugin_menu location="menu_bar/extras/first/scripting/groovy1"/>
   <plugin_property name="ScriptLocation" value="/home/foltin/test.groovy"/>
 </plugin_action>

See also

Personal tools