package ctagsinterface.dockables; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Comparator; import java.util.Vector; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreePath; import javax.swing.JMenuBar; import javax.swing.JMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JCheckBoxMenuItem; import org.gjt.sp.jedit.View; import org.gjt.sp.util.EnhancedTreeCellRenderer; import org.gjt.sp.util.ThreadUtilities; import ctagsinterface.main.CtagsInterfacePlugin; import ctagsinterface.main.Tag; @SuppressWarnings("serial") public class TagBrowser extends JPanel { private View view; private JTree tree; private DefaultTreeModel model; private DefaultMutableTreeNode root; private IMapper mapper; private ISorter sorter; private JRadioButtonMenuItem kind, namespace; private Runnable repaintTree; private Runnable updateTree; private boolean repaintTreeMarked; private JCheckBoxMenuItem caseSensitiveSorting; public TagBrowser(View view) { this.view = view; setLayout(new BorderLayout()); root = new DefaultMutableTreeNode(); model = new DefaultTreeModel(root); tree = new JTree(model); tree.setRootVisible(false); tree.setShowsRootHandles(true); tree.setCellRenderer(new TagTreeCellRenderer()); tree.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { TreePath tp = tree.getPathForLocation(e.getX(), e.getY()); if (tp == null) return; DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent(); if (node.getUserObject() instanceof Tag) { Tag t = (Tag) node.getUserObject(); CtagsInterfacePlugin.jumpToTag(TagBrowser.this.view, t); } super.mouseClicked(e); } }); add(new JScrollPane(tree), BorderLayout.CENTER); JMenuBar groupMenuBar = new JMenuBar(); JMenu groupMenu = new JMenu("Group by"); kind = new JRadioButtonMenuItem("Kind"); namespace = new JRadioButtonMenuItem("Namespace"); groupMenu.add(kind); groupMenu.add(namespace); groupMenuBar.add(groupMenu); ButtonGroup bg = new ButtonGroup(); bg.add(kind); bg.add(namespace); JMenuBar sortMenuBar = new JMenuBar(); JMenu sortMenu = new JMenu("Sorting"); caseSensitiveSorting = new JCheckBoxMenuItem("Case-sensitive"); sortMenu.add(caseSensitiveSorting); sortMenuBar.add(sortMenu); JPanel mapPanel = new JPanel(); mapPanel.add(groupMenuBar); mapPanel.add(sortMenuBar); JButton update = new JButton("Refresh"); JPanel top = new JPanel(new BorderLayout()); add(top, BorderLayout.NORTH); // update first so has highest z-order. top.add(update, BorderLayout.EAST); top.add(mapPanel, BorderLayout.WEST); ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent e) { populate(); } }; kind.addActionListener(listener); namespace.addActionListener(listener); caseSensitiveSorting.addActionListener(listener); update.addActionListener(listener); repaintTree = new Runnable() { public void run() { synchronized(this) { repaintTreeMarked = false; model.nodeStructureChanged(root); } } }; updateTree = new Runnable() { public void run() { mapper = kind.isSelected() ? new KindMapper() : new NamespaceMapper(); sorter = new NameSorter(caseSensitiveSorting.isSelected()); root.removeAllChildren(); model.setRoot(root); Vector<Tag> tags = CtagsInterfacePlugin.runScopedQuery( TagBrowser.this.view, "doctype:tag", 1000000); Vector<Object> path = new Vector<Object>(); for (Tag t: tags) addToTree(t, path); } }; } public void populate() { repaintTreeMarked = false; ThreadUtilities.runInBackground(updateTree); } public void addToTree(Tag t, Vector<Object> path) { if (t.getName().equals("GetPid")) path.clear(); mapper.getPath(t, path); DefaultMutableTreeNode current = root; synchronized(this) { for (Object o: path) current = addChild(current, o); if (! repaintTreeMarked) { repaintTreeMarked = true; ThreadUtilities.runInDispatchThread(repaintTree); } } } private DefaultMutableTreeNode addChild( DefaultMutableTreeNode parent, Object childData) { int low = 0, high = parent.getChildCount() - 1, index = -1; while (high >= low) { index = (low + high) / 2; DefaultMutableTreeNode child = (DefaultMutableTreeNode) parent.getChildAt(index); Object data = child.getUserObject(); int c = sorter.compare(data, childData); if (c < 0) low = index + 1; else if (c > 0) high = index - 1; else if (data instanceof String) { if (childData instanceof Tag) child.setUserObject(childData); return child; } else if (childData instanceof Tag) break; else return child; } if (low > index) index++; DefaultMutableTreeNode child = new DefaultMutableTreeNode(childData); parent.insert(child, index); return child; } private interface IMapper { // Set the given 'path' vector to the path of tag 't'. void getPath(Tag t, Vector<Object> path); } private interface ISorter extends Comparator<Object> { } private static class NamespaceMapper implements IMapper { private static final String GLOBAL_SCOPE = "<< Global (no namespace) >>"; private static final String [] Keywords = { "namespace", "class", "union", "struct", "enum" }; private String separator = "::|\\."; public void getPath(Tag tag, Vector<Object> path) { path.clear(); for (String keyword: Keywords) { String ns = tag.getExtension(keyword); if (ns != null) { String [] parts = ns.split(separator); for (String part: parts) path.add(part); break; } } if (path.isEmpty()) path.add(GLOBAL_SCOPE); path.add(tag); } } private static class KindMapper implements IMapper { public void getPath(Tag t, Vector<Object> path) { path.clear(); path.add(t.getKind()); path.add(t); } } private static class NameSorter implements ISorter { boolean caseSensitive; public NameSorter(boolean caseSensitive) { this.caseSensitive = caseSensitive; } public int compare(Object o1, Object o2) { String s1 = (o1 instanceof Tag) ? ((Tag)o1).getName() : o1.toString(); String s2 = (o2 instanceof Tag) ? ((Tag)o2).getName() : o2.toString(); if (! caseSensitive) { s1 = s1.toLowerCase(); s2 = s2.toLowerCase(); } return s1.compareTo(s2); } } private class TagTreeCellRenderer extends EnhancedTreeCellRenderer { @Override protected void configureTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { setOpenIcon(null); setClosedIcon(null); if (value instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; Object obj = node.getUserObject(); if (obj instanceof Tag) { Tag t = (Tag) obj; setText(t.getName()); ImageIcon icon = t.getIcon(); setIcon(icon); } } } @Override protected TreeCellRenderer newInstance() { return new TagTreeCellRenderer(); } } }