public Spy(String lcmurl) throws IOException
    {
        jf = new JFrame("LCM Spy");
        jdp = new JDesktopPane();
        jdp.setBackground(new Color(0, 0, 160));
        jf.setLayout(new BorderLayout());
        jf.add(jdp, BorderLayout.CENTER);

        jf.setSize(1024, 768);
        jf.setVisible(true);

        //	sortedChannelTableModel.addMouseListenerToHeaderInTable(channelTable);
        channelTableModel.setTableHeader(channelTable.getTableHeader());
        channelTableModel.setSortingStatus(0, TableSorter.ASCENDING);

        handlers = new LCMTypeDatabase();

        TableColumnModel tcm = channelTable.getColumnModel();
        tcm.getColumn(0).setMinWidth(140);
        tcm.getColumn(1).setMinWidth(140);
        tcm.getColumn(2).setMaxWidth(100);
        tcm.getColumn(3).setMaxWidth(100);
        tcm.getColumn(4).setMaxWidth(100);
        tcm.getColumn(5).setMaxWidth(100);
        tcm.getColumn(6).setMaxWidth(100);

        JInternalFrame jif = new JInternalFrame("Channels", true);
        jif.setLayout(new BorderLayout());
        jif.add(channelTable.getTableHeader(), BorderLayout.PAGE_START);
        // XXX weird bug, if clearButton is added after JScrollPane, we get an error.
        jif.add(clearButton, BorderLayout.SOUTH);
        jif.add(new JScrollPane(channelTable), BorderLayout.CENTER);

        jif.setSize(800,600);
        jif.setVisible(true);
        jdp.add(jif);

        if(null == lcmurl)
            lcm = new LCM();
        else
            lcm = new LCM(lcmurl);
        lcm.subscribeAll(new MySubscriber());

        new HzThread().start();

        clearButton.addActionListener(new ActionListener()
	    {
            public void actionPerformed(ActionEvent e)
            {
                channelMap.clear();
                channelList.clear();
                channelTableModel.fireTableDataChanged();
            }
	    });

        channelTable.addMouseListener(new MouseAdapter()
	    {
            public void mouseClicked(MouseEvent e)
            {
                int mods=e.getModifiersEx();

                if (e.getButton()==3)
                {
                    showPopupMenu(e);
                }
                else if (e.getClickCount() == 2)
                {
                    Point p = e.getPoint();
                    int row = rowAtPoint(p);

                    ChannelData cd = channelList.get(row);
                    boolean got_one = false;
                    for (SpyPlugin plugin : plugins)
                    {
                        if (!got_one && plugin.canHandle(cd.fingerprint)) {
                            plugin.getAction(jdp, cd).actionPerformed(null);
                            got_one = true;
                        }
                    }

                    if (!got_one)
                        createViewer(channelList.get(row));
                }
            }
	    });

        jf.addWindowListener(new WindowAdapter()
	    {
            public void windowClosing(WindowEvent e)
            {
                System.out.println("Spy quitting");
                System.exit(0);
            }
	    });

        ClassDiscoverer.findClasses(new PluginClassVisitor());
        System.out.println("Found "+plugins.size()+" plugins");
        for (SpyPlugin plugin : plugins) {
            System.out.println(" "+plugin);
        }
    }