Thursday, September 09, 2010

Some Freemind Scripting

I go back and forth, trying to find my ultimate tool for knowledge and task management. I have been switching between TiddlyWiki, email, Freemind, and other wikis and online/local tools. The pendulum has swung back to freemind for the moment, and I'm finally getting around to scripting what I need in it. As some background, Freemind is my preferred mind-mapping application, whether online or on my desktop. I also keep coming back to it as my knowledge management tool.

I downloaded the source code of freemind, since documentation is non-existent. What is documented is that there is a groovy scripting environment. The main classes used are MindMapController and MindMapNode. The controller is used to make and changes to your mindmap, and the node is used for reading and traversing. The environment provides you c as the current MindMapController and node  as the current node. Just so you know, I am by no means writing idiomatic groovy. I am using Freemind version 0.9 RC7

With this bit of background I wrote the following short script:


import freemind.modes.MindMapNode;
import freemind.modes.mindmapmode.MindMapController;
import freemind.modes.attributes.Attribute;
import java.text.SimpleDateFormat;

def now = new java.util.Date();
def fmt = "yyyy-MM-dd HH:mm";
def formatter = new SimpleDateFormat(fmt);
def nowString = formatter.format(now);
def status = node.getAttribute("task");
if(status == null){
    def attr = new Attribute("task","todo");
    c.addAttribute(node,attr);
}
else{
    c.editAttribute(node,"task","todo");
}
if((pTS=node.getAttribute("task-changed"))==null){
    def timestamp = new Attribute("task-changed",nowString);
    c.addAttribute(node,timestamp);
}
else{
    c.editAttribute(node,"task-changed",nowString);
}


What it does is to add a pair of attributes to a node.

At first, what I attempted to do was to associate it with a pattern. In other words, from the context menu for a node, I went to Physical Styles -> Manage Patterns -> Needs Actionand at the bottom of that page opened the script editor, inserting the above script. I ran it from the script editor, and it acted as expected, to my pleasant surprise. Then I saved and exited, assuming that every time I hit F4 on a node, the script would be run on that node. Alas, that was not the case. In fact, to my chagrin, when I opened up that attached script again, I found pieces had been cut off, and all my formatting was gone. With a little probing, I found that the script was saved in my personal patterns.xml file, and probably the saving and parsing isn't quite working right.


Next step. I just saved the script to a node. That is, with a node selected, I went to Tools -> Script Editor, pasted in the script, and then hit ALT-F8 to evaluate it. That worked.


The approach I am going with now is to associate the script with the root node. As a first step, I look for any node that starts with the string "todo::". If that is present, I create attributes indicating this node is a task, and the time at which it was set as a task, and remove one of the colons from that prefix string, so that the node doesn't get reprocessed. Then I can use a filter to find just the task management nodes. Granted, I could do this with filters, but this allows me to add some tags on when a task was created and when it was completed.


Here is the final script


import freemind.modes.MindMapNode;
import freemind.modes.mindmapmode.MindMapController;
import freemind.modes.attributes.Attribute;
import java.text.SimpleDateFormat;

def setTags(MindMapNode pnode, MindMapController pc, String pstatus){
    def now = new java.util.Date();
    def fmt = "yyyy-MM-dd HH:mm";
    def formatter = new SimpleDateFormat(fmt);
    def nowString = formatter.format(now);
    def status = pnode.getAttribute("task");
    if(status == null){
        def attr = new Attribute("task", pstatus);
        pc.addAttribute(pnode,attr);
    }
    else{
        pc.editAttribute(pnode,"task", pstatus);
    }
    def pTS=pnode.getAttribute("time-" + pstatus);
    if(pTS==null){
        def timestamp = new Attribute("time-" + pstatus , nowString);
        pc.addAttribute(pnode,timestamp);
    }
    else{
        pc.editAttribute(pnode,"time-" + pstatus, nowString);
    }
}

def processTasks(MindMapNode pnode, MindMapController pc){
    // check if text is tagged
    def nodeText=pnode.getText();
    def matchTodo = (nodeText =~ /todo::/);
    if(matchTodo){
        setTags(pnode, pc, "todo");
        nodeText = matchTodo.replaceFirst("todo:");
        pc.setNodeText(pnode,nodeText);
    }
    if(pnode.hasChildren()){
        pnode.childrenUnfolded().each() { aChildNode ->
            processTasks(aChildNode, pc); /* recursive invocation */
            def placeHolder = 1;
        };
    };
}

processTasks(node, c);