Recently I updated a PDF viewer application, add a bookmark tree to the viewer. Here is my solution:
- Use ITextPDF library to get the bookmark.
- BookmarkNode.java: represents a bookmark node in the tree.
- BookmarkTreeModel.java: tree model.
- BookmarkTreeBuilder.java: build a tree of BookmarkNodes recursively, based on the bookmark retrieved from ITextPDF.
- BookmarkTreeNodeRenderer.java: renderer for a bookmark node.
- BookmarkTreeSelectionListener.java: selection listener for the tree. When a node is clicked, the program goes to the appropriate page.
Code of the BookmarkNode.java class:
import java.util.ArrayList;
import java.util.List;
/**
* Represents a node in bookmark tree
* @author Ha Minh Nam
*/
public class BookmarkNode {
private String title;
private int pageNumber;
private List<BookmarkNode> childNodes;
public BookmarkNode(String title) {
this.title = title;
this.childNodes = new ArrayList();
}
public String toString() {
return this.title;
}
public void addChild(BookmarkNode child) {
childNodes.add(child);
}
public void removeChild(BookmarkNode child) {
childNodes.remove(child);
}
public int getChildCount() {
return childNodes.size();
}
public BookmarkNode getChildAt(int index) {
return childNodes.get(index);
}
public int getIndexOfChild(BookmarkNode child) {
return childNodes.indexOf(child);
}
/**
* @return the pageNumber
*/
public int getPageNumber() {
return pageNumber;
}
/**
* @param pageNumber the pageNumber to set
*/
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
}
}
Code of the BookmarkTreeModel.java class:
import java.util.ArrayList;
import java.util.List;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
/**
* Represents a tree model for bookmark view
* @author Ha Minh Nam
*/
public class BookmarkTreeModel implements TreeModel {
private BookmarkNode rootNode;
private List<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
public BookmarkTreeModel(BookmarkNode rootNode) {
this.rootNode = rootNode;
}
public Object getRoot() {
return rootNode;
}
public Object getChild(Object parent, int index) {
BookmarkNode parentNode = (BookmarkNode) parent;
return parentNode.getChildAt(index);
}
public int getChildCount(Object parent) {
BookmarkNode parentNode = (BookmarkNode) parent;
return parentNode.getChildCount();
}
public boolean isLeaf(Object node) {
BookmarkNode parentNode = (BookmarkNode) node;
return parentNode.getChildCount() == 0;
}
public void valueForPathChanged(TreePath path, Object newValue) {
}
public int getIndexOfChild(Object parent, Object child) {
BookmarkNode parentNode = (BookmarkNode) parent;
BookmarkNode childNode = (BookmarkNode) child;
return parentNode.getIndexOfChild(childNode);
}
public void addTreeModelListener(TreeModelListener l) {
listeners.add(l);
}
public void removeTreeModelListener(TreeModelListener l) {
listeners.remove(l);
}
}
Code of the BookmarkTreeBuilder.java class:
import java.util.HashMap;
import java.util.List;
/**
* Builds bookmark as a tree
* @author Ha Minh Nam
*/
public final class BookmarkTreeBuilder {
public static BookmarkNode buildBookmark(List<HashMap<String, Object>> bookmarks) {
BookmarkNode rootNode = new BookmarkNode("Root");
for (HashMap<String, Object> aBookmark : bookmarks) {
Object title = aBookmark.get("Title");
BookmarkNode node = new BookmarkNode(title != null ? title.toString() : "");
Object actionObj = aBookmark.get("Action");
String action = actionObj != null ? actionObj.toString() : "";
if ("GoTo".equals(action)) {
Object pageObj = aBookmark.get("Page");
if (pageObj != null) {
String page = aBookmark.get("Page").toString();
String pageNum = page.split(" ")[0];
node.setPageNumber(Integer.parseInt(pageNum));
} else {
node.setPageNumber(-3);
}
} else {
node.setPageNumber(-3);
}
rootNode.addChild(node);
buildBookmarkRecursive(aBookmark, node);
}
return rootNode;
}
private static void buildBookmarkRecursive(HashMap<String, Object> parentBookmark,
BookmarkNode parentNode) {
List<HashMap<String, Object>> subBookmarks = (List<HashMap<String, Object>>) parentBookmark.get("Kids");
if (subBookmarks != null && !subBookmarks.isEmpty()) {
for (HashMap aSubBookmark : subBookmarks) {
Object title = aSubBookmark.get("Title");
BookmarkNode node = new BookmarkNode(title != null ? title.toString() : "");
Object actionObj = aSubBookmark.get("Action");
String action = actionObj != null ? actionObj.toString() : "";
if ("GoTo".equals(action)) {
Object pageObj = aSubBookmark.get("Page");
if (pageObj != null) {
String page = aSubBookmark.get("Page").toString();
String pageNum = page.split(" ")[0];
node.setPageNumber(Integer.parseInt(pageNum));
} else {
node.setPageNumber(-3);
}
} else {
node.setPageNumber(-3);
}
parentNode.addChild(node);
buildBookmarkRecursive(aSubBookmark, node);
}
}
}
}
Code of the BookmarkTreeNodeRenderer.java class:
import java.awt.Component;
import javax.swing.ImageIcon;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;
/**
* A render is used to set icon for tree node.
* @author Ha Minh Nam
*/
public class BookmarkTreeNodeRenderer extends DefaultTreeCellRenderer {
private ImageIcon bookmarkLeaf
= new ImageIcon(getClass().getResource("/bookmark/icons/bookmark_leaf.png"));
private ImageIcon bookmarkBranch
= new ImageIcon(getClass().getResource("/bookmark/icons/bookmark_branch.png"));
private ImageIcon bookmarkOpen
= new ImageIcon(getClass().getResource("/bookmark/icons/bookmark_open.png"));
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (leaf) {
setIcon(bookmarkLeaf);
} else {
if (expanded) {
setIcon(bookmarkOpen);
} else {
setIcon(bookmarkBranch);
}
}
return this;
}
}
Code of the BookmarkTreeSelectionListener.java class:
import com.sun.pdfview.PDFViewer;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
/**
* Listens to tree selection events.
* @author Ha Minh Nam
*/
public class BookmarkTreeSelectionListener implements TreeSelectionListener {
private JTree tree;
private PDFViewer viewer;
public BookmarkTreeSelectionListener(JTree tree, PDFViewer viewer) {
this.tree = tree;
this.viewer = viewer;
}
public void valueChanged(TreeSelectionEvent e) {
Object node = tree.getLastSelectedPathComponent();
if (node == null) {
return;
}
BookmarkNode bookmark = (BookmarkNode) node;
System.out.println("Go to: " + bookmark.getPageNumber());
viewer.gotoPage(bookmark.getPageNumber() - 1);
}
}
The viewer is the PDF viewer application. It implements the gotoPage(page_number) method. The following method is to create the bookmark tree. You can place this method inside a container such as a JPanel:
public void load(List<HashMap<String, Object>> bookmarks) {
BookmarkTreeModel model = new BookmarkTreeModel(BookmarkTreeBuilder.buildBookmark(bookmarks));
treeBookmark.setModel(model);
treeBookmark.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
treeBookmark.setRowHeight(20);
treeBookmark.setCellRenderer(new BookmarkTreeNodeRenderer());
treeBookmark.setToggleClickCount(1);
treeBookmark.setRootVisible(false);
treeBookmark.addTreeSelectionListener(new BookmarkTreeSelectionListener(treeBookmark, viewer));
for (int i = 0; i < treeBookmark.getRowCount(); i++) {
treeBookmark.expandRow(i);
}
}
where treeBookmark is an instance JTree class. The following code to load the bookmark data:
private List<HashMap<String, Object>> bookmarks;
// load bookmarks
try {
PdfReader reader = new PdfReader(fileName);
bookmarks = SimpleBookmark.getBookmark(reader);
reader.close();
} catch (IOException ex) {
ex.printStackTrace();
}

wonder if i put jython into netbeans… will it be a good thing?