private SymbolGTree createTree(SymbolTreeRootNode rootNode) {
		if (tree != null) {
			tree.setRootNode(rootNode);
			return tree;
		}

		SymbolGTree newTree = new SymbolGTree(rootNode, plugin);

		newTree.addGTreeSelectionListener(e -> {

			EventOrigin origin = e.getEventOrigin();
			if (origin != EventOrigin.USER_GENERATED) {
				contextChanged();
				return;
			}

			maybeGoToSymbol();
			contextChanged();
		});

		newTree.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {

				// This code serves to perform navigation in  the case that the selection handler
				// above does not, as is the case when the node is already selected.  This code
				// will get called on the mouse release, whereas the selection handler gets called
				// on the mouse pressed.
				// For now, just attempt to perform the goto.  It may get called twice, but this
				// should have no real impact on performance.

				maybeGoToSymbol();
			}
		});

		newTree.addTreeExpansionListener(new TreeExpansionListener() {

			@Override
			public void treeExpanded(TreeExpansionEvent event) {
				// nothing
			}

			@Override
			public void treeCollapsed(TreeExpansionEvent event) {
				treeNodeCollapsed(event.getPath());
			}
		});

		newTree.setEditable(true);

		return newTree;
	}
A list into which tasks to be run will accumulated until we put them into the GTree's
task system.  We do this because the tasks run so fast that we can get too much thread
creation from the GTree's worker.  By buffering these behind a SwingUpdateManager, we will
prevent to much work from happening too fast.  Also, we perform the work in a bulk task
so that the tree can benefit from optimizations made by the bulk task.
Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java