package edu.umd.cs.findbugs.gui2;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import edu.umd.cs.findbugs.I18N;
import edu.umd.cs.findbugs.Project;
import edu.umd.cs.findbugs.filter.Filter;
import edu.umd.cs.findbugs.filter.Matcher;
import edu.umd.cs.findbugs.gui2.FilterActivity.FilterActivityNotifier;

public class MainFrameTree implements Serializable {
    private final MainFrame mainFrame;

    JTree tree;

    SorterTableColumnModel sorter;

    JTableHeader tableheader;

    BugLeafNode currentSelectedBugLeaf;

    JPanel treePanel;

    JScrollPane treeScrollPane;

    JPopupMenu bugPopupMenu;

    JPopupMenu branchPopupMenu;

    JPanel cardPanel;

    private JTextField textFieldForPackagesToDisplay;

    private JLabel waitStatusLabel;

    public MainFrameTree(MainFrame mainFrame) {
        this.mainFrame = mainFrame;
    }

    public void newTree(final JTree newTree, final BugTreeModel newModel) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                tree = newTree;
                tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
                tree.setLargeModel(true);
                tree.setCellRenderer(new BugRenderer());
                treePanel.remove(treeScrollPane);
                treeScrollPane = new JScrollPane(newTree);
                treePanel.add(treeScrollPane);
                mainFrame.setFontSizeHelper(Driver.getFontSize(), treeScrollPane);
                tree.setRowHeight((int) (Driver.getFontSize() + 7));
                mainFrame.getContentPane().validate();
                mainFrame.getContentPane().repaint();

                setupTreeListeners();
                newModel.openPreviouslySelected(((BugTreeModel) (tree.getModel())).getOldSelectedBugs());
                expandTree(10);
                expandToFirstLeaf(14);
                mainFrame.getSorter().addColumnModelListener(newModel);
                FilterActivity.addFilterListener(newModel.bugTreeFilterListener);
                mainFrame.mainFrameTree.setSorting(true);

            }
        });
    }

    public JTree getTree() {
        return tree;
    }

    public BugTreeModel getBugTreeModel() {
        return (BugTreeModel) getTree().getModel();
    }

    public Sortables[] getAvailableSortables() {
        Sortables[] sortables;
        ArrayList<Sortables> a = new ArrayList<Sortables>(Sortables.values().length);
        for (Sortables s : Sortables.values())
            if (s.isAvailable(mainFrame))
                a.add(s);
        sortables = new Sortables[a.size()];
        a.toArray(sortables);
        return sortables;
    }

    /**
     * Returns the SorterTableColumnModel of the MainFrame.
     * 
     * @return
     */
    SorterTableColumnModel getSorter() {
        return sorter;
    }

    void rebuildBugTreeIfSortablesDependOnCloud() {
        BugTreeModel bt = (BugTreeModel) (mainFrame.getTree().getModel());
        List<Sortables> sortables = sorter.getOrderBeforeDivider();
        if (sortables.contains(Sortables.DESIGNATION) || sortables.contains(Sortables.FIRST_SEEN)
                || sortables.contains(Sortables.FIRSTVERSION) || sortables.contains(Sortables.LASTVERSION)) {

            bt.rebuild();
        }
    }

    public void updateBugTree() {
        mainFrame.acquireDisplayWait();
        try {
            BugTreeModel model = (BugTreeModel) getTree().getModel();
            BugSet bs;
            if (mainFrame.getBugCollection() != null) {
                bs = new BugSet(mainFrame.getBugCollection());
            } else {
                bs = new BugSet(Collections.<BugLeafNode>emptySet());
            }
            model.getOffListenerList();
            model.changeSet(bs);
            if (bs.size() == 0 && bs.sizeUnfiltered() > 0) {
                warnUserOfFilters();
            }

            mainFrame.updateStatusBar();
            mainFrame.updateTitle();
        } finally {
            mainFrame.releaseDisplayWait();
        }
    }

    private void warnUserOfFilters() {
        JOptionPane
                .showMessageDialog(
                        mainFrame,
                        edu.umd.cs.findbugs.L10N
                                .getLocalString("dlg.everything_is_filtered",
                                        "All bugs in this project appear to be filtered out.  \nYou may wish to check your filter settings in the preferences menu."),
                        "Warning", JOptionPane.WARNING_MESSAGE);
    }

    /**
     * Creates popup menu for bugs on tree.
     * 
     * @return
     */
    JPopupMenu createBugPopupMenu() {
        JPopupMenu popupMenu = new JPopupMenu();

        JMenuItem filterMenuItem = MainFrameHelper.newJMenuItem("menu.filterBugsLikeThis", "Filter bugs like this");

        filterMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                if (!mainFrame.canNavigateAway()) { return; }
                
                new NewFilterFromBug(new FilterFromBugPicker(currentSelectedBugLeaf.getBug(), 
                                                             Arrays.asList(mainFrame.getAvailableSortables())),
                                     new ApplyNewFilter(mainFrame.getProject().getSuppressionFilter(), 
                                                        PreferencesFrame.getInstance(), 
                                                        new FilterActivityNotifier()));

                mainFrame.setProjectChanged(true);
                mainFrame.getTree().setSelectionRow(0); // Selects the top of the Jtree so the CommentsArea syncs up.
            }
        });

        popupMenu.add(filterMenuItem);

        JMenu changeDesignationMenu = MainFrameHelper.newJMenu("menu.changeDesignation", "Change bug designation");

        int i = 0;
        int keyEvents[] = { KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4, KeyEvent.VK_5, KeyEvent.VK_6,
                KeyEvent.VK_7, KeyEvent.VK_8, KeyEvent.VK_9 };
        for (String key : I18N.instance().getUserDesignationKeys(true)) {
            String name = I18N.instance().getUserDesignation(key);
            mainFrame.addDesignationItem(changeDesignationMenu, key, name, keyEvents[i++]);
        }

        popupMenu.add(changeDesignationMenu);

        return popupMenu;
    }

    /**
     * Creates the branch pop up menu that ask if the user wants to hide all the
     * bugs in that branch.
     * 
     * @return
     */
    JPopupMenu createBranchPopUpMenu() {
        JPopupMenu popupMenu = new JPopupMenu();

        JMenuItem filterMenuItem = MainFrameHelper.newJMenuItem("menu.filterTheseBugs", "Filter these bugs");

        filterMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                // TODO This code does a smarter version of filtering that is
                // only possible for branches, and does so correctly
                // However, it is still somewhat of a hack, because if we ever
                // add more tree listeners than simply the bugtreemodel,
                // They will not be called by this code. Using FilterActivity to
                // notify all listeners will however destroy any
                // benefit of using the smarter deletion method.

                try {
                    if (!mainFrame.canNavigateAway())
                        return;
                    int startCount;
                    TreePath path = MainFrame.getInstance().getTree().getSelectionPath();
                    TreePath deletePath = path;
                    startCount = ((BugAspects) (path.getLastPathComponent())).getCount();
                    int count = ((BugAspects) (path.getParentPath().getLastPathComponent())).getCount();
                    while (count == startCount) {
                        deletePath = deletePath.getParentPath();
                        if (deletePath.getParentPath() == null)// We are at the
                                                               // top of the
                                                               // tree, don't
                                                               // let this be
                                                               // removed,
                                                               // rebuild tree
                                                               // from root.
                        {
                            Matcher m = mainFrame.getCurrentSelectedBugAspects().getMatcher();
                            Filter suppressionFilter = MainFrame.getInstance().getProject().getSuppressionFilter();
                            suppressionFilter.addChild(m);
                            PreferencesFrame.getInstance().updateFilterPanel();
                            FilterActivity.notifyListeners(FilterListener.Action.FILTERING, null);
                            return;
                        }
                        count = ((BugAspects) (deletePath.getParentPath().getLastPathComponent())).getCount();
                    }
                    /*
                     * deletePath should now be a path to the highest ancestor
                     * branch with the same number of elements as the branch to
                     * be deleted in other words, the branch that we actually
                     * have to remove in order to correctly remove the selected
                     * branch.
                     */
                    BugTreeModel model = MainFrame.getInstance().getBugTreeModel();
                    TreeModelEvent event = new TreeModelEvent(mainFrame, deletePath.getParentPath(),
                            new int[] { model.getIndexOfChild(deletePath.getParentPath().getLastPathComponent(),
                                    deletePath.getLastPathComponent()) }, new Object[] { deletePath.getLastPathComponent() });
                    Matcher m = mainFrame.getCurrentSelectedBugAspects().getMatcher();
                    Filter suppressionFilter = MainFrame.getInstance().getProject().getSuppressionFilter();
                    suppressionFilter.addChild(m);
                    PreferencesFrame.getInstance().updateFilterPanel();
                    model.sendEvent(event, BugTreeModel.TreeModification.REMOVE);
                    // FilterActivity.notifyListeners(FilterListener.Action.FILTERING,
                    // null);

                    mainFrame.setProjectChanged(true);

                    MainFrame.getInstance().getTree().setSelectionRow(0);// Selects
                                                                         // the
                                                                         // top
                                                                         // of
                                                                         // the
                                                                         // Jtree
                                                                         // so
                                                                         // the
                                                                         // CommentsArea
                                                                         // syncs
                                                                         // up.
                } catch (RuntimeException e) {
                    MainFrame.getInstance().showMessageDialog("Unable to create filter: " + e.getMessage());
                }
            }
        });

        popupMenu.add(filterMenuItem);

        JMenu changeDesignationMenu = MainFrameHelper.newJMenu("menu.changeDesignation", "Change bug designation");

        int i = 0;
        int keyEvents[] = { KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4, KeyEvent.VK_5, KeyEvent.VK_6,
                KeyEvent.VK_7, KeyEvent.VK_8, KeyEvent.VK_9 };
        for (String key : I18N.instance().getUserDesignationKeys(true)) {
            String name = I18N.instance().getUserDesignation(key);
            mainFrame.addDesignationItem(changeDesignationMenu, key, name, keyEvents[i++]);
        }

        popupMenu.add(changeDesignationMenu);

        return popupMenu;
    }

    ActionListener treeActionAdapter(ActionMap map, String actionName) {
        final Action selectPrevious = map.get(actionName);
        return new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                e.setSource(tree);
                selectPrevious.actionPerformed(e);
            }
        };
    }

    @SwingThread
    void expandTree(int max) {
        Debug.printf("expandTree(%d)\n", max);
        JTree jTree = getTree();
        int i = 0;
        while (true) {
            int rows = jTree.getRowCount();
            if (i >= rows || rows >= max)
                break;
            jTree.expandRow(i++);
        }
    }

    @SwingThread
    boolean leavesShown() {
        JTree jTree = getTree();

        int rows = jTree.getRowCount();
        for (int i = 0; i < rows; i++) {
            TreePath treePath = jTree.getPathForRow(i);
            Object lastPathComponent = treePath.getLastPathComponent();
            if (lastPathComponent instanceof BugLeafNode)
                return true;
        }
        return false;
    }

    @SwingThread
    void expandToFirstLeaf(int max) {
        Debug.println("expand to first leaf");
        if (leavesShown())
            return;
        JTree jTree = getTree();
        int i = 0;
        while (true) {
            int rows = jTree.getRowCount();
            if (i >= rows || rows >= max)
                break;
            TreePath treePath = jTree.getPathForRow(i);
            Object lastPathComponent = treePath.getLastPathComponent();
            if (lastPathComponent instanceof BugLeafNode)
                return;
            jTree.expandRow(i++);
        }
    }

    void setupTreeListeners() {
        // noinspection ConstantIfStatement
        if (false)
            tree.addTreeExpansionListener(new MyTreeExpansionListener());
        tree.addTreeSelectionListener(new MyTreeSelectionListener());

        tree.addMouseListener(new TreeMouseListener());
    }

    void setSorting(boolean b) {
        tableheader.setReorderingAllowed(b);
    }

    Sortables[] sortables() {
        return Sortables.values();
    }

    public BugLeafNode getCurrentSelectedBugLeaf() {
        return currentSelectedBugLeaf;
    }

    public JPanel bugListPanel() {
        tableheader = new JTableHeader();
        getTableheader().setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
        // Listener put here for when user double clicks on sorting
        // column header SorterDialog appears.
        getTableheader().addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                Debug.println("tableheader.getReorderingAllowed() = " + getTableheader().getReorderingAllowed());
                if (!getTableheader().getReorderingAllowed())
                    return;
                if (e.getClickCount() == 2)
                    SorterDialog.getInstance().setVisible(true);
            }

            @Override
            public void mouseReleased(MouseEvent arg0) {
                if (!getTableheader().getReorderingAllowed())
                    return;
                BugTreeModel bt = (BugTreeModel) (getTree().getModel());
                bt.checkSorter();
            }
        });
        sorter = GUISaveState.getInstance().getStarterTable();
        getTableheader().setColumnModel(getSorter());
        getTableheader().setToolTipText(
                edu.umd.cs.findbugs.L10N.getLocalString("tooltip.reorder_message", "Drag to reorder tree folder and sort order"));

        tree = new JTree();
        getTree().setLargeModel(true);
        getTree().getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        getTree().setCellRenderer(new BugRenderer());
        getTree().setRowHeight((int) (Driver.getFontSize() + 7));
        getTree().setModel(new BugTreeModel(mainFrame, getTree(), getSorter(), new BugSet(new ArrayList<BugLeafNode>())));
        setupTreeListeners();
        mainFrame.setProject(new Project());

        treeScrollPane = new JScrollPane(getTree());

        treePanel = new JPanel(new BorderLayout());
        treePanel.add(treeScrollPane, BorderLayout.CENTER);
        JTable t = new JTable(new DefaultTableModel(0, sortables().length));
        t.setTableHeader(getTableheader());

        textFieldForPackagesToDisplay = new JTextField();
        ActionListener filterAction = new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                    String text = textFieldForPackagesToDisplay.getText();
                    if (text.indexOf('/') >= 0) {
                        text = text.replace('/', '.');
                        textFieldForPackagesToDisplay.setText(text);
                    }
                    mainFrame.getViewFilter().setPackagesToDisplay(text);
                    mainFrame.resetViewCache();
                } catch (IllegalArgumentException err) {
                    JOptionPane.showMessageDialog(mainFrame, err.getMessage(), "Bad class search string",
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        };
        textFieldForPackagesToDisplay.addActionListener(filterAction);
        JButton filterButton = new JButton("Filter");
        filterButton.addActionListener(filterAction);
        JPanel filterPanel = new JPanel();
        filterPanel.setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = 1;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.gridy = 1;
        filterPanel.add(textFieldForPackagesToDisplay, gbc);

        gbc.weightx = 0;
        gbc.fill = GridBagConstraints.NONE;
        filterPanel.add(filterButton, gbc);

        filterPanel.setToolTipText("Only show classes containing the word(s) you specify");

        JPanel sortablePanel = new JPanel(new GridBagLayout());
        JLabel sortableLabel = new JLabel("Group bugs by:");
        sortableLabel.setLabelFor(getTableheader());
        gbc = new GridBagConstraints();
        gbc.weightx = 0;
        gbc.gridy = 1;
        gbc.insets = new Insets(3, 3, 3, 3);
        gbc.fill = GridBagConstraints.BOTH;
        sortablePanel.add(sortableLabel, gbc);
        gbc.weightx = 1;
        sortablePanel.add(getTableheader(), gbc);

        getTableheader().setBorder(new LineBorder(Color.BLACK));

        JPanel topPanel = makeNavigationPanel("Class name filter:", filterPanel, sortablePanel, treePanel);
        cardPanel = new JPanel(new CardLayout());
        JPanel waitPanel = new JPanel();
        waitPanel.setLayout(new BoxLayout(waitPanel, BoxLayout.Y_AXIS));
        waitPanel.add(new JLabel("Please wait..."));
        waitStatusLabel = new JLabel();
        waitPanel.add(waitStatusLabel);
        cardPanel.add(topPanel, MainFrame.BugCard.TREECARD.name());
        cardPanel.add(waitPanel, MainFrame.BugCard.WAITCARD.name());
        return cardPanel;
    }

    public JTableHeader getTableheader() {
        return tableheader;
    }

    public void setBugPopupMenu(JPopupMenu bugPopupMenu) {
        this.bugPopupMenu = bugPopupMenu;
    }

    public void setBranchPopupMenu(JPopupMenu branchPopupMenu) {
        this.branchPopupMenu = branchPopupMenu;
    }

    void updateFonts(float size) {
        bugPopupMenu.setFont(bugPopupMenu.getFont().deriveFont(size));
        mainFrame.setFontSizeHelper(size, bugPopupMenu.getComponents());

        branchPopupMenu.setFont(branchPopupMenu.getFont().deriveFont(size));
        mainFrame.setFontSizeHelper(size, branchPopupMenu.getComponents());
    }

    void showCard(final MainFrame.BugCard card, final Cursor cursor, final Window window) {
        Runnable doRun = new Runnable() {
            public void run() {
                mainFrame.enableRecentMenu(card == MainFrame.BugCard.TREECARD);
                getTableheader().setReorderingAllowed(card == MainFrame.BugCard.TREECARD);
                mainFrame.getMainFrameMenu().enablePreferencesMenuItem(card == MainFrame.BugCard.TREECARD);
                window.setCursor(cursor);
                CardLayout layout = (CardLayout) cardPanel.getLayout();
                layout.show(cardPanel, card.name());
                if (card == MainFrame.BugCard.TREECARD)
                    SorterDialog.getInstance().thaw();
                else
                    SorterDialog.getInstance().freeze();
            }
        };
        if (SwingUtilities.isEventDispatchThread())
            doRun.run();
        else
            SwingUtilities.invokeLater(doRun);
    }

    private JPanel makeNavigationPanel(String packageSelectorLabel, JComponent packageSelector, JComponent treeHeader,
            JComponent tree) {
        JPanel topPanel = new JPanel();
        topPanel.setMinimumSize(new Dimension(150, 150));

        topPanel.setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.ipadx = c.ipady = 3;
        c.insets = new Insets(6, 6, 6, 6);
        c.gridx = 0;
        c.gridy = 0;
        c.fill = GridBagConstraints.NONE;
        JLabel label = new JLabel(packageSelectorLabel);
        topPanel.add(label, c);

        c.gridx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weightx = 1;
        topPanel.add(packageSelector, c);

        c.gridx = 0;
        c.gridwidth = 2;
        c.gridy++;
        c.ipadx = c.ipady = 2;
        c.fill = GridBagConstraints.HORIZONTAL;
        topPanel.add(treeHeader, c);

        c.fill = GridBagConstraints.BOTH;
        c.gridy++;
        c.weighty = 1;
        c.ipadx = c.ipady = 0;
        c.insets = new Insets(0, 0, 0, 0);
        topPanel.add(tree, c);
        return topPanel;
    }

    public void setWaitStatusLabelText(String msg) {
        waitStatusLabel.setText(msg);
    }

    public void setFieldForPackagesToDisplayText(String filter) {
        textFieldForPackagesToDisplay.setText(filter);
    }

    private class TreeMouseListener implements MouseListener {
        public void mouseClicked(MouseEvent e) {
            TreePath path = tree.getPathForLocation(e.getX(), e.getY());

            if (path == null)
                return;

            if ((e.getButton() == MouseEvent.BUTTON3) || (e.getButton() == MouseEvent.BUTTON1 && e.isControlDown())) {

                if (tree.getModel().isLeaf(path.getLastPathComponent())) {
                    tree.setSelectionPath(path);
                    bugPopupMenu.show(tree, e.getX(), e.getY());
                } else {
                    tree.setSelectionPath(path);
                    if (!(path.getParentPath() == null))// If the path's parent
                                                        // path is null, the
                                                        // root was selected,
                                                        // dont allow them to
                                                        // filter out the root.
                        branchPopupMenu.show(tree, e.getX(), e.getY());
                }
            }
        }

        public void mousePressed(MouseEvent arg0) {
        }

        public void mouseReleased(MouseEvent arg0) {
        }

        public void mouseEntered(MouseEvent arg0) {
        }

        public void mouseExited(MouseEvent arg0) {
        }
    }

    private class MyTreeSelectionListener implements TreeSelectionListener {
        private volatile boolean ignoreSelection = false;
        public void valueChanged(TreeSelectionEvent selectionEvent) {
            if (ignoreSelection)
                return;

            TreePath path = selectionEvent.getNewLeadSelectionPath();
            if (path != null) {
                if (!mainFrame.canNavigateAway()) {
                    try {
                        ignoreSelection = true;
                        tree.clearSelection();
                        tree.setSelectionPath(selectionEvent.getOldLeadSelectionPath());
                    } finally {
                        ignoreSelection = false;
                    }
                    return;
                }

                Object lastPathComponent = path.getLastPathComponent();
                if (lastPathComponent instanceof BugLeafNode) {
                    boolean beforeProjectChanged = mainFrame.isProjectChanged();
                    currentSelectedBugLeaf = (BugLeafNode) lastPathComponent;
                    mainFrame.setCurrentSelectedBugAspects(null);
                    mainFrame.syncBugInformation();
                    mainFrame.setProjectChanged(beforeProjectChanged);
                } else {
                    boolean beforeProjectChanged = mainFrame.isProjectChanged();
                    mainFrame.updateDesignationDisplay();
                    currentSelectedBugLeaf = null;
                    mainFrame.setCurrentSelectedBugAspects((BugAspects) lastPathComponent);
                    mainFrame.syncBugInformation();
                    mainFrame.setProjectChanged(beforeProjectChanged);
                }
            }
        }
    }

    private class MyTreeExpansionListener implements TreeExpansionListener {

        public void treeExpanded(TreeExpansionEvent event) {
            System.out.println("Tree expanded");
            TreePath path = event.getPath();
            Object lastPathComponent = path.getLastPathComponent();
            int children = tree.getModel().getChildCount(lastPathComponent);
            if (children == 1) {
                Object o = tree.getModel().getChild(lastPathComponent, 0);
                if (o instanceof BugAspects) {
                    final TreePath p = path.pathByAddingChild(o);
                    SwingUtilities.invokeLater(new Runnable() {

                        public void run() {
                            try {
                                System.out.println("auto expanding " + p);
                                tree.expandPath(p);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }

                        }
                    });
                }

            }
        }

        public void treeCollapsed(TreeExpansionEvent event) {
            // do nothing
        }
    }
}
