Learn how easy it is to sync an existing GitHub or Google Code repo to a SourceForge project! See Demo


Search for attribute values

  • Is it possible to search for attribute values?
    Ctrl+F finds nodes only.

    • Ken Robertson
      Ken Robertson

      G'day Frank,
      Yes you can filter for attributes.  Click the filter funnel icon (4th from the left), click Edit to open the Filter Composer, click the Node Text drop-down, that drop-down listing should contain "Node Text", "Icon", plus your attribute names, choose your condition, and the final drop down will contain your attribute values, click Add to add your filter to the list.  Select it and click Apply to apply.  ) I confess to having difficulty with negated filters.)

      Once you have a few filters added to the list, you can combine them, Ctrl click to select, then "And" or "Or" your selections.  Finally, save your filters if you're inclined and you can load them intyo any other map.


    • Ken, thank you for the detailed explanation!
      As a workaround it will help me a lot to find the values, but it's very time consuming to handle every search with a filter.
      For filtering the attribute name it would be ok, because there are a lot of nodes with the same attribute(s). But searching for a single value is very hard, as I always have to change the filter rule and FreeMind does not focus on the nodes that are left over or were selected during the switch of filters.

      I'm using attributes to add synonyms and translations to the nodes. That's why I have to search for them, to find a special node.

  • Cliff Neil
    Cliff Neil

    Although, this thread is not the youngest one: I have to agree with Frank. Ctrl + F should search attribute values as well. That's what I expect as a user. It's not clear to me why Ctrl + F searches nodes only. Lots of information are stored in the attributes and there should be a fast and easy way to search them. Using filters might do the job but calling this a "work around" is the exact description of what happens here. It's simply too heavy.

    Guys, please extend the find feature to attributes as well.

  • Cliff Neil
    Cliff Neil

    I did it myself …

    /*FreeMind - A Program for creating and viewing Mindmaps
     *Copyright (C) 2000-2006 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others.
     *See COPYING for Details
     *This program is free software; you can redistribute it and/or
     *modify it under the terms of the GNU General Public License
     *as published by the Free Software Foundation; either version 2
     *of the License, or (at your option) any later version.
     *This program is distributed in the hope that it will be useful,
     *but WITHOUT ANY WARRANTY; without even the implied warranty of
     *GNU General Public License for more details.
     *You should have received a copy of the GNU General Public License
     *along with this program; if not, write to the Free Software
     *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    package freemind.modes.common.actions;
    import java.awt.event.ActionEvent;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.ListIterator;
    import javax.swing.AbstractAction;
    import javax.swing.ImageIcon;
    import javax.swing.JOptionPane;
    import freemind.main.HtmlTools;
    import freemind.modes.ControllerAdapter;
    import freemind.modes.MindMapNode;
    public class FindAction extends AbstractAction {
        private final ControllerAdapter controller;
        private ArrayList findNodesUnfoldedByLastFind;
        private MindMapNode findFromNode;
        private String searchTerm;
        private Collection subterms;
         * @return Returns the subterms.
        public Collection getSubterms() {
            return subterms;
        public String getSearchTerm() {
            return searchTerm;
        public String getFindFromText() {
            final String plainNodeText = HtmlTools.htmlToPlain(findFromNode.toString()).replaceAll("\n"," ");
            return plainNodeText.length() <= 30
                    ? plainNodeText
                            : plainNodeText.substring(0,30)+"...";
        private boolean findCaseSensitive;
        private LinkedList findNodeQueue;
        public FindAction(final ControllerAdapter controller) {
            super(controller.getText("find"), new ImageIcon(controller
            this.controller = controller;
        public void actionPerformed(final ActionEvent e) {
            final String what = JOptionPane.showInputDialog(controller.getView()
                    .getSelected(), controller.getText("find_what"), controller
                    .getText("find"), JOptionPane.QUESTION_MESSAGE);
            if (what == null || what.equals("")) {
            final Collection subterms = breakSearchTermIntoSubterms(what);
            this.searchTerm = what;
            final boolean found = find(controller.getSelected(), subterms, /* caseSensitive= */
            if (!found) {
                final String messageText = controller.getText("no_found_from");
                final String searchTerm = messageText.startsWith("<html>")
                        ? HtmlTools.toXMLEscapedText(getSearchTerm())
                                : getSearchTerm();
                                replaceAll("\\$1", searchTerm).
                                replaceAll("\\$2", getFindFromText()),
        public static class FindNextAction extends AbstractAction {
            private final ControllerAdapter controller;
            private final FindAction find;
            public FindNextAction(final ControllerAdapter controller, final FindAction find) {
                this.controller = controller;
                this.find = find;
            public void actionPerformed(final ActionEvent e) {
                final Collection subterms = find.getSubterms();
                if (subterms == null) {
                final boolean found = find.findNext();
                if (!found) {
                    final String messageText = controller.getText("no_more_found_from");
                    final String searchTerm = messageText.startsWith("<html>")
                            ? HtmlTools.toXMLEscapedText(find.getSearchTerm())
                                    : find.getSearchTerm();
                                    replaceAll("\\$1", searchTerm).
                                    replaceAll("\\$2", find.getFindFromText()),
        public boolean find(final MindMapNode node, final Collection subterms, final boolean caseSensitive) {
            findNodesUnfoldedByLastFind = new ArrayList();
            final LinkedList nodes = new LinkedList();
            findFromNode = node;
            Collection finalizedSubterms;
            if (!caseSensitive) {
                finalizedSubterms = new ArrayList();
                for (final Iterator i = subterms.iterator(); i.hasNext(); ) {
                    finalizedSubterms.add(((String)i.next()).toLowerCase()); }}
            else {
                finalizedSubterms = subterms; }
            return find(nodes, finalizedSubterms, caseSensitive); }
        private boolean find(final LinkedList /* queue of MindMapNode */nodes,
                final Collection subterms, final boolean caseSensitive) {
            // Precondition: if !caseSensitive then >>what<< is in lowercase.
            // Fold the path of previously found node
            final boolean thereWereNodesToBeFolded = !findNodesUnfoldedByLastFind
            if (!findNodesUnfoldedByLastFind.isEmpty()) {
                // if (false) {
                final ListIterator i = findNodesUnfoldedByLastFind
                while (i.hasPrevious()) {
                    final MindMapNode node = (MindMapNode) i.previous();
                    try {
                        controller.setFolded(node, true);
                    } catch (final Exception e) {
                findNodesUnfoldedByLastFind = new ArrayList();
            // We implement width-first search.
            while (!nodes.isEmpty()) {
                final MindMapNode node = (MindMapNode) nodes.removeFirst();
                // Add children to the queue
                for (final ListIterator i = node.childrenUnfolded(); i.hasNext();) {
                if(! node.isVisible())
                // Bug fix for http://sourceforge.net/tracker/?func=detail&aid=3035387&group_id=7118&atid=107118
                String nodeText = node.toString();
                if (HtmlTools.isHtmlNode(nodeText)) {
                    nodeText = HtmlTools.unescapeHTMLUnicodeEntity(nodeText);
                    nodeText = HtmlTools.removeHtmlTagsFromString(nodeText);
                if(!caseSensitive) {
                    nodeText = nodeText.toLowerCase();
                // End bug fix.
                String subTerm = "";
                boolean found = false;
                for (final Iterator i = subterms.iterator(); i.hasNext();) {
                    subTerm = (String) i.next();
                    if (nodeText.indexOf(subTerm) >= 0 ) { // Subterm found
                        found = true;
                    } else {
                        // Search attributes (keys and values) as well in case
                        // the previous search wasn't successful so far. Added by
                        // Cliff Neil.
                        final List attributeKeyList = node.getAttributeKeyList();
                        if(attributeKeyList == null || attributeKeyList.isEmpty())
                            continue; // No attributes. Go ahead.
                        String key = "", value = "";
                        for(final Object o : attributeKeyList) {
                            key = o.toString();
                            value = node.getAttribute(key);
                                    || (value != null && value.contains(subTerm))) {
                                found = true;
                if (found) { // Found
                    displayNode(node, findNodesUnfoldedByLastFind);
                    // Save the state for find next
                    this.subterms = subterms;
                    findCaseSensitive = caseSensitive;
                    findNodeQueue = nodes;
                    return true;
            return false;
        private Collection breakSearchTermIntoSubterms(final String searchTerm) {
            final ArrayList subterms = new ArrayList();
            final StringBuffer subterm = new StringBuffer();
            final int len = searchTerm.length();
            char myChar;
            char previousChar = 'a';
            boolean withinQuotes = false;
            for (int i = 0; i < len; ++i) {
                myChar = searchTerm.charAt(i);
                if (myChar == ' ' && withinQuotes ) {
                    subterm.append(myChar); }
                else if ((myChar == ' ' && !withinQuotes )) {
                    subterm.setLength(0); }
                else if (myChar == '"' &&
                        i > 0 && i < len-1 &&
                        searchTerm.charAt(i-1) != ' ' &&
                        searchTerm.charAt(i+1) != ' ') {
                    // Character " surrounded by non-spaces
                    subterm.append(myChar); }
                else if (myChar == '"' && withinQuotes) {
                    withinQuotes = false; }
                else if (myChar == '"' && !withinQuotes) {
                    withinQuotes = true; }
                else {
                    subterm.append(myChar); }
                previousChar = myChar; }
            return subterms; }
         * Display a node in the display (used by find and the goto action by arrow
         * link actions).
        public void displayNode(final MindMapNode node, final ArrayList nodesUnfoldedByDisplay) {
            // Unfold the path to the node
            final Object[] path = controller.getMap().getPathToRoot(node);
            // Iterate the path with the exception of the last node
            for (int i = 0; i < path.length - 1; i++) {
                final MindMapNode nodeOnPath = (MindMapNode) path[i];
                if (nodeOnPath.isFolded()) {
                    if (nodesUnfoldedByDisplay != null)
                    controller.setFolded(nodeOnPath, false);
        public boolean findNext() {
            // Precodition: subterms != null. We check the precodition but give no
            // message.
            // The logic of find next is vulnerable. find next relies on the queue
            // of nodes from previous find / find next. However, between previous
            // find / find next and this find next, nodes could have been deleted
            // or moved. The logic expects that no changes happened, even that no
            // node has been folded / unfolded.
            // You may want to come with more correct solution, but this one
            // works for most uses, and does not cause any big trouble except
            // perhaps for some uncaught exceptions. As a result, it is not very
            // nice, but far from critical and working quite fine.
            if (subterms != null) {
                return find(findNodeQueue, subterms, findCaseSensitive);
            return false;
        private void centerNode(final MindMapNode node) {
            // Select the node and scroll to it.