/* Jreepad - personal information manager. Copyright (C) 2004-2006 Dan Stowell 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 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. The full license can be read online here: http://www.gnu.org/copyleft/gpl.html */ package jreepad; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; /** * The tree node. Contains an article. * * @version $Id: JreepadNode.java,v 1.27 2007-01-26 22:49:04 pewu Exp $ */ public class JreepadNode extends DefaultMutableTreeNode implements Comparable { public JreepadNode() { this(new JreepadArticle()); } public JreepadNode(String content) { this(new JreepadArticle(content)); } public JreepadNode(String title, String content) { this(new JreepadArticle(title, content)); } public JreepadNode(JreepadArticle article) { setUserObject(article); } public JreepadArticle getArticle() { return (JreepadArticle)getUserObject(); } public String toFullString() { String ret = "JreepadNode \"" + getTitle() + "\": " + getChildCount() + " direct child nodes in subtree"; ret += "\nDirect children:"; for (int i = 0; i < children.size(); i++) ret += "\n " + ((JreepadNode)getChildAt(i)).getTitle(); return ret; } public String getWikiAnchor() { if (getParent() == null) return htmlSpecialChars(getTitle()); return getParentNode().getWikiAnchor() + "/" + htmlSpecialChars(getTitle()); } private static String htmlSpecialChars(String in) { char[] c = in.toCharArray(); StringBuffer ret = new StringBuffer(); for (int i = 0; i < c.length; i++) if (c[i] == '<') ret.append("<"); else if (c[i] == '>') ret.append(">"); else if (c[i] == '&') ret.append("&"); else if (c[i] == '\n') ret.append(" <br />\n"); else if (c[i] == '"') ret.append("""); else ret.append(c[i]); return ret.toString(); } public String exportTitlesAsList() { return exportTitlesAsList(0).toString(); } private StringBuffer exportTitlesAsList(int currentDepth) { StringBuffer ret = new StringBuffer(); for (int i = 0; i < currentDepth; i++) ret.append(" "); ret.append(getTitle() + "\n"); for (int i = 0; i < children.size(); i++) ret.append(((JreepadNode)getChildAt(i)).exportTitlesAsList(currentDepth + 1)); return ret; } public String exportArticlesToText(boolean titlesToo) { // System.out.println("Expooort beginning"); return exportArticlesToTextRecursive(titlesToo).toString(); } public StringBuffer exportArticlesToTextRecursive(boolean titlesToo) { // System.out.println("Expooort " + getTitle()); StringBuffer ret = new StringBuffer(); if (titlesToo) ret.append(getTitle() + "\n\n"); ret.append(getArticle().getContent() + "\n\n"); for (int i = 0; i < children.size(); i++) ret.append(((JreepadNode)getChildAt(i)).exportArticlesToTextRecursive(titlesToo)); return ret; } public JreepadNode removeChild(int child) // Can be used to delete, OR to 'get' one for moving { JreepadNode ret = (JreepadNode)getChildAt(child); remove(child); return ret; } public boolean indent() { // Get position in parent. If zero or -1 then return. int pos = getIndex(); if (pos < 1) return false; // Get sibling node just above, and move self to there. MutableTreeNode oldParent = (MutableTreeNode)getParent(); DefaultMutableTreeNode newParent = (DefaultMutableTreeNode)oldParent.getChildAt(pos - 1); newParent.add(this); return true; } public boolean outdent() { // Get parent's parent. If null then return. JreepadNode p = (JreepadNode)getParent(); if (p == null) return false; JreepadNode pp = (JreepadNode)p.getParent(); if (pp == null) return false; // Get parent's position in its parent. = ppos int ppos = p.getIndex(); // Move self to parent's parent, at (ppos+1) p.removeChild(getIndex()); pp.insert(this, ppos + 1); // Also (as in the original treepad) move all the later siblings so they're children of this // node // NOT DONE YET return true; } public void moveUp() { MutableTreeNode parent = (MutableTreeNode)getParent(); if (parent == null) return; int index = getIndex(); if (index < 1) return; parent.insert(this, index - 1); } public void moveDown() { MutableTreeNode parent = (MutableTreeNode)getParent(); if (parent == null) return; int index = getIndex(); if (index < 0 || index >= parent.getChildCount() - 1) return; parent.insert(this, index + 1); } public JreepadNode addChild() { JreepadNode theChild = new JreepadNode(); add(theChild); return theChild; } public JreepadNode addChild(int index) { JreepadNode theChild = new JreepadNode(); insert(theChild, index); return theChild; } public int getIndex() { if (getParent() == null) return -1; return getParent().getIndex(this); } public void sortChildrenRecursive() { sortChildren(); for (int i = 0; i < getChildCount(); i++) ((JreepadNode)getChildAt(i)).sortChildrenRecursive(); } // Function for using Java's built-in mergesort public void sortChildren() { Object[] childrenArray = children.toArray(); java.util.Arrays.sort(childrenArray); removeAllChildren(); for (int i = 0; i < childrenArray.length; i++) add((JreepadNode)childrenArray[i]); } // The following function is a halfway-house on the way to "natural numerical ordering" public int compareTo(Object o) { String a = getTitle(); String b = ((JreepadNode)o).getTitle(); if (a.length() != 0 && b.length() != 0 && Character.isDigit(a.charAt(0)) && Character.isDigit(b.charAt(0))) { // Both strings begin with digits - so implement natural numerical ordering here StringBuffer aBuf = new StringBuffer(""); StringBuffer bBuf = new StringBuffer(""); int i; for (i = 0; i < a.length(); i++) if (Character.isDigit(a.charAt(i))) aBuf.append(a.charAt(i)); else break; for (i = 0; i < b.length(); i++) if (Character.isDigit(b.charAt(i))) bBuf.append(b.charAt(i)); else break; return (new Integer(aBuf.toString())).compareTo(new Integer(bBuf.toString())); } return a.compareToIgnoreCase(b); } public boolean isLeaf() { return (getChildCount() == 0); } public JreepadNode getParentNode() { return (JreepadNode)getParent(); } public void addChildFromTextFile(InputStreamReader textFile, String nodeName) throws IOException { // Load the content as a string StringBuffer contentString = new StringBuffer(); String currentLine; BufferedReader bReader = new BufferedReader(textFile); while ((currentLine = bReader.readLine()) != null) contentString.append(currentLine + "\n"); // Then just create the node add(new JreepadNode(new JreepadArticle(nodeName, contentString.toString()))); } // This getCopy() function is intended to return a copy of the entire subtree, used for Undo public JreepadNode getCopy() { JreepadNode ret = new JreepadNode(getArticle()); for (int i = 0; i < getChildCount(); i++) { ret.add(((JreepadNode)getChildAt(i)).getCopy()); } return ret; } public JreepadNode getChildByTitle(String title) { for (int i = 0; i < getChildCount(); i++) if (((JreepadNode)getChildAt(i)).getTitle().equals(title)) return (JreepadNode)getChildAt(i); return null; } public String getTitle() { return getArticle().getTitle(); } public void setTitle(String title) { getArticle().setTitle(title); } public String getContent() { return getArticle().getContent(); } /* // Listens for edits that can be undone. protected class JreepadNodeUndoableEditListener implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { //System.out.println("Undoable event is " + (e.getEdit().isSignificant()?"":"NOT ") + "significant"); //System.out.println("Undoable event source: " + e.getEdit()); //Remember the edit and update the menus. undoMgr.addEdit(e.getEdit()); //undoAction.updateUndoState(); //redoAction.updateRedoState(); } } */ }