src/jdk.jconsole/share/classes/sun/tools/jconsole/ConnectDialog.java
changeset 47216 71c04702a3d5
parent 25859 3317bb8137f4
child 52902 e3398b2e1ab0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jconsole/share/classes/sun/tools/jconsole/ConnectDialog.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,768 @@
+/*
+ * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.tools.jconsole;
+
+import java.util.List;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import javax.swing.plaf.basic.BasicRadioButtonUI;
+import javax.swing.table.*;
+
+
+
+import static java.awt.BorderLayout.*;
+import static javax.swing.ListSelectionModel.*;
+import static sun.tools.jconsole.Utilities.*;
+
+@SuppressWarnings("serial")
+public class ConnectDialog extends InternalDialog
+                implements DocumentListener, FocusListener,
+                           ItemListener, ListSelectionListener, KeyListener {
+
+    private static final int COL_NAME = 0;
+    private static final int COL_PID  = 1;
+
+
+    JConsole jConsole;
+    JTextField userNameTF, passwordTF;
+    JRadioButton localRadioButton, remoteRadioButton;
+    JLabel localMessageLabel, remoteMessageLabel;
+    JTextField remoteTF;
+    JButton connectButton, cancelButton;
+    JPanel radioButtonPanel;
+
+    private Icon mastheadIcon =
+        new MastheadIcon(Messages.CONNECT_DIALOG_MASTHEAD_TITLE);
+    private Color hintTextColor, disabledTableCellColor;
+
+    // The table of managed VM (local process)
+    JTable vmTable;
+    ManagedVmTableModel vmModel = null;
+
+    JScrollPane localTableScrollPane = null;
+
+    private Action connectAction, cancelAction;
+
+
+    public ConnectDialog(JConsole jConsole) {
+        super(jConsole, Messages.CONNECT_DIALOG_TITLE, true);
+
+        this.jConsole = jConsole;
+        setAccessibleDescription(this,
+                                 Messages.CONNECT_DIALOG_ACCESSIBLE_DESCRIPTION);
+        setDefaultCloseOperation(HIDE_ON_CLOSE);
+        setResizable(false);
+        Container cp = (JComponent)getContentPane();
+
+        radioButtonPanel = new JPanel(new BorderLayout(0, 12));
+        radioButtonPanel.setBorder(new EmptyBorder(6, 12, 12, 12));
+        ButtonGroup radioButtonGroup = new ButtonGroup();
+        JPanel bottomPanel = new JPanel(new BorderLayout());
+
+        statusBar = new JLabel(" ", JLabel.CENTER);
+        setAccessibleName(statusBar,
+                          Messages.CONNECT_DIALOG_STATUS_BAR_ACCESSIBLE_NAME);
+
+        Font normalLabelFont = statusBar.getFont();
+        Font boldLabelFont = normalLabelFont.deriveFont(Font.BOLD);
+        Font smallLabelFont = normalLabelFont.deriveFont(normalLabelFont.getSize2D() - 1);
+
+        JLabel mastheadLabel = new JLabel(mastheadIcon);
+        setAccessibleName(mastheadLabel,
+                          Messages.CONNECT_DIALOG_MASTHEAD_ACCESSIBLE_NAME);
+
+        cp.add(mastheadLabel, NORTH);
+        cp.add(radioButtonPanel, CENTER);
+        cp.add(bottomPanel, SOUTH);
+
+        createActions();
+
+        remoteTF = new JTextField();
+        remoteTF.addActionListener(connectAction);
+        remoteTF.getDocument().addDocumentListener(this);
+        remoteTF.addFocusListener(this);
+        remoteTF.setPreferredSize(remoteTF.getPreferredSize());
+        setAccessibleName(remoteTF,
+                          Messages.REMOTE_PROCESS_TEXT_FIELD_ACCESSIBLE_NAME);
+
+        //
+        // If the VM supports the local attach mechanism (is: Sun
+        // implementation) then the Local Process panel is created.
+        //
+        if (JConsole.isLocalAttachAvailable()) {
+            vmModel = new ManagedVmTableModel();
+            vmTable = new LocalTabJTable(vmModel);
+            vmTable.setSelectionMode(SINGLE_SELECTION);
+            vmTable.setPreferredScrollableViewportSize(new Dimension(400, 250));
+            vmTable.setColumnSelectionAllowed(false);
+            vmTable.addFocusListener(this);
+            vmTable.getSelectionModel().addListSelectionListener(this);
+
+            TableColumnModel columnModel = vmTable.getColumnModel();
+
+            TableColumn pidColumn = columnModel.getColumn(COL_PID);
+            pidColumn.setMaxWidth(getLabelWidth("9999999"));
+            pidColumn.setResizable(false);
+
+            TableColumn cmdLineColumn = columnModel.getColumn(COL_NAME);
+            cmdLineColumn.setResizable(false);
+
+            localRadioButton = new JRadioButton(Messages.LOCAL_PROCESS_COLON);
+            localRadioButton.setMnemonic(Resources.getMnemonicInt(Messages.LOCAL_PROCESS_COLON));
+            localRadioButton.setFont(boldLabelFont);
+            localRadioButton.addItemListener(this);
+            radioButtonGroup.add(localRadioButton);
+
+            JPanel localPanel = new JPanel(new BorderLayout());
+
+            JPanel localTablePanel = new JPanel(new BorderLayout());
+
+            radioButtonPanel.add(localPanel, NORTH);
+
+            localPanel.add(localRadioButton, NORTH);
+            localPanel.add(new Padder(localRadioButton), LINE_START);
+            localPanel.add(localTablePanel, CENTER);
+
+            localTableScrollPane = new JScrollPane(vmTable);
+
+            localTablePanel.add(localTableScrollPane, NORTH);
+
+            localMessageLabel = new JLabel(" ");
+            localMessageLabel.setFont(smallLabelFont);
+            localMessageLabel.setForeground(hintTextColor);
+            localTablePanel.add(localMessageLabel, SOUTH);
+        }
+
+        remoteRadioButton = new JRadioButton(Messages.REMOTE_PROCESS_COLON);
+        remoteRadioButton.setMnemonic(Resources.getMnemonicInt(Messages.REMOTE_PROCESS_COLON));
+        remoteRadioButton.setFont(boldLabelFont);
+        radioButtonGroup.add(remoteRadioButton);
+
+        JPanel remotePanel = new JPanel(new BorderLayout());
+        if (localRadioButton != null) {
+            remotePanel.add(remoteRadioButton, NORTH);
+            remotePanel.add(new Padder(remoteRadioButton), LINE_START);
+
+            Action nextRadioButtonAction =
+                new AbstractAction("nextRadioButton") {
+                    public void actionPerformed(ActionEvent ev) {
+                        JRadioButton rb =
+                            (ev.getSource() == localRadioButton) ? remoteRadioButton
+                                                                 : localRadioButton;
+                        rb.doClick();
+                        rb.requestFocus();
+                    }
+                };
+
+            localRadioButton.getActionMap().put("nextRadioButton", nextRadioButtonAction);
+            remoteRadioButton.getActionMap().put("nextRadioButton", nextRadioButtonAction);
+
+            localRadioButton.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
+                                               "nextRadioButton");
+            remoteRadioButton.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
+                                                "nextRadioButton");
+        } else {
+            JLabel remoteLabel = new JLabel(remoteRadioButton.getText());
+            remoteLabel.setFont(boldLabelFont);
+            remotePanel.add(remoteLabel, NORTH);
+        }
+        radioButtonPanel.add(remotePanel, SOUTH);
+
+        JPanel remoteTFPanel = new JPanel(new BorderLayout());
+        remotePanel.add(remoteTFPanel, CENTER);
+
+        remoteTFPanel.add(remoteTF, NORTH);
+
+        remoteMessageLabel = new JLabel("<html>" + Messages.REMOTE_TF_USAGE + "</html>");
+        remoteMessageLabel.setFont(smallLabelFont);
+        remoteMessageLabel.setForeground(hintTextColor);
+        remoteTFPanel.add(remoteMessageLabel, CENTER);
+
+        JPanel userPwdPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
+        userPwdPanel.setBorder(new EmptyBorder(12, 0, 0, 0)); // top padding
+
+        int tfWidth = JConsole.IS_WIN ? 12 : 8;
+
+        userNameTF = new JTextField(tfWidth);
+        userNameTF.addActionListener(connectAction);
+        userNameTF.getDocument().addDocumentListener(this);
+        userNameTF.addFocusListener(this);
+        setAccessibleName(userNameTF,
+            Messages.USERNAME_ACCESSIBLE_NAME);
+        LabeledComponent lc;
+        lc = new LabeledComponent(Messages.USERNAME_COLON_,
+                                  Resources.getMnemonicInt(Messages.USERNAME_COLON_),
+                                  userNameTF);
+        lc.label.setFont(boldLabelFont);
+        userPwdPanel.add(lc);
+
+        passwordTF = new JPasswordField(tfWidth);
+        // Heights differ, so fix here
+        passwordTF.setPreferredSize(userNameTF.getPreferredSize());
+        passwordTF.addActionListener(connectAction);
+        passwordTF.getDocument().addDocumentListener(this);
+        passwordTF.addFocusListener(this);
+        setAccessibleName(passwordTF,
+            Messages.PASSWORD_ACCESSIBLE_NAME);
+
+        lc = new LabeledComponent(Messages.PASSWORD_COLON_,
+                                  Resources.getMnemonicInt(Messages.PASSWORD_COLON_),
+                                  passwordTF);
+        lc.setBorder(new EmptyBorder(0, 12, 0, 0)); // Left padding
+        lc.label.setFont(boldLabelFont);
+        userPwdPanel.add(lc);
+
+        remoteTFPanel.add(userPwdPanel, SOUTH);
+
+        String connectButtonToolTipText =
+            Messages.CONNECT_DIALOG_CONNECT_BUTTON_TOOLTIP;
+        connectButton = new JButton(connectAction);
+        connectButton.setToolTipText(connectButtonToolTipText);
+
+        cancelButton = new JButton(cancelAction);
+
+        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+        buttonPanel.setBorder(new EmptyBorder(12, 12, 2, 12));
+        if (JConsole.IS_GTK) {
+            buttonPanel.add(cancelButton);
+            buttonPanel.add(connectButton);
+        } else {
+            buttonPanel.add(connectButton);
+            buttonPanel.add(cancelButton);
+        }
+        bottomPanel.add(buttonPanel, NORTH);
+
+        bottomPanel.add(statusBar, SOUTH);
+
+        updateButtonStates();
+        Utilities.updateTransparency(this);
+    }
+
+    public void revalidate() {
+        // Adjust some colors
+        Color disabledForeground = UIManager.getColor("Label.disabledForeground");
+        if (disabledForeground == null) {
+            // fall back for Nimbus that doesn't support 'Label.disabledForeground'
+            disabledForeground = UIManager.getColor("Label.disabledText");
+        }
+        hintTextColor =
+            ensureContrast(disabledForeground,
+                           UIManager.getColor("Panel.background"));
+        disabledTableCellColor =
+            ensureContrast(new Color(0x808080),
+                           UIManager.getColor("Table.background"));
+
+        if (remoteMessageLabel != null) {
+            remoteMessageLabel.setForeground(hintTextColor);
+            // Update html color setting
+            String colorStr =
+                String.format("%06x", hintTextColor.getRGB() & 0xFFFFFF);
+            remoteMessageLabel.setText("<html><font color=#" + colorStr + ">" +
+                                       Messages.REMOTE_TF_USAGE);
+        }
+        if (localMessageLabel != null) {
+            localMessageLabel.setForeground(hintTextColor);
+            // Update html color setting
+            valueChanged(null);
+        }
+
+        super.revalidate();
+    }
+
+    private void createActions() {
+        connectAction = new AbstractAction(Messages.CONNECT) {
+            /* init */ {
+                putValue(Action.MNEMONIC_KEY, Resources.getMnemonicInt(Messages.CONNECT));
+            }
+
+            public void actionPerformed(ActionEvent ev) {
+                if (!isEnabled() || !isVisible()) {
+                    return;
+                }
+                setVisible(false);
+                statusBar.setText("");
+
+                if (remoteRadioButton.isSelected()) {
+                    String txt = remoteTF.getText().trim();
+                    String userName = userNameTF.getText().trim();
+                    userName = userName.equals("") ? null : userName;
+                    String password = passwordTF.getText();
+                    password = password.equals("") ? null : password;
+                    try {
+                        if (txt.startsWith(JConsole.ROOT_URL)) {
+                            String url = txt;
+                            jConsole.addUrl(url, userName, password, false);
+                            remoteTF.setText(JConsole.ROOT_URL);
+                            return;
+                        } else {
+                            String host = remoteTF.getText().trim();
+                            String port = "0";
+                            int index = host.lastIndexOf(':');
+                            if (index >= 0) {
+                                port = host.substring(index + 1);
+                                host = host.substring(0, index);
+                            }
+                            if (host.length() > 0 && port.length() > 0) {
+                                int p = Integer.parseInt(port.trim());
+                                jConsole.addHost(host, p, userName, password);
+                                remoteTF.setText("");
+                                userNameTF.setText("");
+                                passwordTF.setText("");
+                                return;
+                            }
+                        }
+                    } catch (Exception ex) {
+                        statusBar.setText(ex.toString());
+                    }
+                    setVisible(true);
+                } else if (localRadioButton != null && localRadioButton.isSelected()) {
+                    // Try to connect to selected VM. If a connection
+                    // cannot be established for some reason (the process has
+                    // terminated for example) then keep the dialog open showing
+                    // the connect error.
+                    //
+                    int row = vmTable.getSelectedRow();
+                    if (row >= 0) {
+                        jConsole.addVmid(vmModel.vmAt(row));
+                    }
+                    refresh();
+                }
+            }
+        };
+
+        cancelAction = new AbstractAction(Messages.CANCEL) {
+            public void actionPerformed(ActionEvent ev) {
+                setVisible(false);
+                statusBar.setText("");
+            }
+        };
+    }
+
+
+    // a label used solely for calculating the width
+    private static JLabel tmpLabel = new JLabel();
+    public static int getLabelWidth(String text) {
+        tmpLabel.setText(text);
+        return (int) tmpLabel.getPreferredSize().getWidth() + 1;
+    }
+
+    private class LocalTabJTable extends JTable {
+        ManagedVmTableModel vmModel;
+        Border rendererBorder = new EmptyBorder(0, 6, 0, 6);
+
+        public LocalTabJTable(ManagedVmTableModel model) {
+            super(model);
+            this.vmModel = model;
+
+            // Remove vertical lines, expect for GTK L&F.
+            // (because GTK doesn't show header dividers)
+            if (!JConsole.IS_GTK) {
+                setShowVerticalLines(false);
+                setIntercellSpacing(new Dimension(0, 1));
+            }
+
+            // Double-click handler
+            addMouseListener(new MouseAdapter() {
+                public void mouseClicked(MouseEvent evt) {
+                    if (evt.getClickCount() == 2) {
+                        connectButton.doClick();
+                    }
+                }
+            });
+
+            // Enter should call default action
+            getActionMap().put("connect", connectAction);
+            InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "connect");
+        }
+
+        public String getToolTipText(MouseEvent e) {
+            String tip = null;
+            java.awt.Point p = e.getPoint();
+            int rowIndex = rowAtPoint(p);
+            int colIndex = columnAtPoint(p);
+            int realColumnIndex = convertColumnIndexToModel(colIndex);
+
+            if (realColumnIndex == COL_NAME) {
+                LocalVirtualMachine vmd = vmModel.vmAt(rowIndex);
+                tip = vmd.toString();
+            }
+            return tip;
+        }
+
+        public TableCellRenderer getCellRenderer(int row, int column) {
+            return new DefaultTableCellRenderer() {
+                public Component getTableCellRendererComponent(JTable table,
+                                                               Object value,
+                                                               boolean isSelected,
+                                                               boolean hasFocus,
+                                                               int row,
+                                                               int column) {
+                    Component comp =
+                        super.getTableCellRendererComponent(table, value, isSelected,
+                                                            hasFocus, row, column);
+
+                    if (!isSelected) {
+                        LocalVirtualMachine lvm = vmModel.vmAt(row);
+                        if (!lvm.isManageable() && !lvm.isAttachable()) {
+                            comp.setForeground(disabledTableCellColor);
+                        }
+                    }
+
+                    if (comp instanceof JLabel) {
+                        JLabel label = (JLabel)comp;
+                        label.setBorder(rendererBorder);
+
+                        if (value instanceof Integer) {
+                            label.setHorizontalAlignment(JLabel.RIGHT);
+                        }
+                    }
+
+                    return comp;
+                }
+            };
+        }
+    }
+
+    public void setConnectionParameters(String url,
+                                        String host,
+                                        int port,
+                                        String userName,
+                                        String password,
+                                        String msg) {
+        if ((url != null && url.length() > 0) ||
+            (host != null && host.length() > 0 && port > 0)) {
+
+            remoteRadioButton.setSelected(true);
+            if (url != null && url.length() > 0) {
+                remoteTF.setText(url);
+            } else {
+                remoteTF.setText(host+":"+port);
+            }
+            userNameTF.setText((userName != null) ? userName : "");
+            passwordTF.setText((password != null) ? password : "");
+
+            statusBar.setText((msg != null) ? msg : "");
+            if (getPreferredSize().width > getWidth()) {
+                pack();
+            }
+            remoteTF.requestFocus();
+            remoteTF.selectAll();
+        }
+    }
+
+
+    public void itemStateChanged(ItemEvent ev) {
+        if (!localRadioButton.isSelected()) {
+            vmTable.getSelectionModel().clearSelection();
+        }
+        updateButtonStates();
+    }
+
+    private void updateButtonStates() {
+        boolean connectEnabled = false;
+
+        if (remoteRadioButton.isSelected()) {
+            connectEnabled = JConsole.isValidRemoteString(remoteTF.getText());
+        } else if (localRadioButton != null && localRadioButton.isSelected()) {
+            int row = vmTable.getSelectedRow();
+            if (row >= 0) {
+                LocalVirtualMachine lvm = vmModel.vmAt(row);
+                connectEnabled = (lvm.isManageable() || lvm.isAttachable());
+            }
+        }
+
+        connectAction.setEnabled(connectEnabled);
+    }
+
+    public void insertUpdate(DocumentEvent e) {
+        updateButtonStates();
+    }
+
+    public void removeUpdate(DocumentEvent e) {
+        updateButtonStates();
+    }
+
+    public void changedUpdate(DocumentEvent e) {
+        updateButtonStates();
+    }
+
+    public void focusGained(FocusEvent e) {
+        Object source = e.getSource();
+        Component opposite = e.getOppositeComponent();
+
+        if (!e.isTemporary() &&
+            source instanceof JTextField &&
+            opposite instanceof JComponent &&
+            SwingUtilities.getRootPane(opposite) == getRootPane()) {
+
+            ((JTextField)source).selectAll();
+        }
+
+        if (source == remoteTF) {
+            remoteRadioButton.setSelected(true);
+        } else if (source == vmTable) {
+            localRadioButton.setSelected(true);
+            if (vmModel.getRowCount() == 1) {
+                // if there's only one process then select the row
+                vmTable.setRowSelectionInterval(0, 0);
+            }
+        }
+        updateButtonStates();
+    }
+
+    public void focusLost(FocusEvent e) {
+    }
+
+    public void keyTyped(KeyEvent e) {
+        char c = e.getKeyChar();
+        if (c == KeyEvent.VK_ESCAPE) {
+            setVisible(false);
+        } else if (!(Character.isDigit(c) ||
+                     c == KeyEvent.VK_BACK_SPACE ||
+                     c == KeyEvent.VK_DELETE)) {
+            getToolkit().beep();
+            e.consume();
+        }
+    }
+
+    public void setVisible(boolean b) {
+        boolean wasVisible = isVisible();
+        super.setVisible(b);
+        if (b && !wasVisible) {
+            SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    if (remoteRadioButton.isSelected()) {
+                        remoteTF.requestFocus();
+                        remoteTF.selectAll();
+                    }
+                }
+            });
+        }
+    }
+
+    public void keyPressed(KeyEvent e) {
+    }
+
+    public void keyReleased(KeyEvent e) {
+    }
+
+
+    // ListSelectionListener interface
+    public void valueChanged(ListSelectionEvent e) {
+        updateButtonStates();
+        String labelText = " "; // Non-empty to reserve vertical space
+        int row = vmTable.getSelectedRow();
+        if (row >= 0) {
+            LocalVirtualMachine lvm = vmModel.vmAt(row);
+            if (!lvm.isManageable()) {
+                if (lvm.isAttachable()) {
+                    labelText = Messages.MANAGEMENT_WILL_BE_ENABLED;
+                } else {
+                    labelText = Messages.MANAGEMENT_NOT_ENABLED;
+                }
+            }
+        }
+        String colorStr =
+            String.format("%06x", hintTextColor.getRGB() & 0xFFFFFF);
+        localMessageLabel.setText("<html><font color=#" + colorStr + ">" + labelText);
+    }
+    // ----
+
+
+    // Refresh the list of managed VMs
+    public void refresh() {
+        if (vmModel != null) {
+            // Remember selection
+            LocalVirtualMachine selected = null;
+            int row = vmTable.getSelectedRow();
+            if (row >= 0) {
+                selected = vmModel.vmAt(row);
+            }
+
+            vmModel.refresh();
+
+            int selectRow = -1;
+            int n = vmModel.getRowCount();
+            if (selected != null) {
+                for (int i = 0; i < n; i++) {
+                    LocalVirtualMachine lvm = vmModel.vmAt(i);
+                    if (selected.vmid() == lvm.vmid() &&
+                        selected.toString().equals(lvm.toString())) {
+
+                        selectRow = i;
+                        break;
+                    }
+                }
+            }
+            if (selectRow > -1) {
+                vmTable.setRowSelectionInterval(selectRow, selectRow);
+            } else {
+                vmTable.getSelectionModel().clearSelection();
+            }
+
+            Dimension dim = vmTable.getPreferredSize();
+
+            // Tricky. Reduce height by one to avoid double line at bottom,
+            // but that causes a scroll bar to appear, so remove it.
+            dim.height = Math.min(dim.height-1, 100);
+            localTableScrollPane.setVerticalScrollBarPolicy((dim.height < 100)
+                                                ? JScrollPane.VERTICAL_SCROLLBAR_NEVER
+                                                : JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+            localTableScrollPane.getViewport().setMinimumSize(dim);
+            localTableScrollPane.getViewport().setPreferredSize(dim);
+        }
+        pack();
+        setLocationRelativeTo(jConsole);
+    }
+
+    // Represents the list of managed VMs as a tabular data model.
+    private static class ManagedVmTableModel extends AbstractTableModel {
+        private static String[] columnNames = {
+            Messages.COLUMN_NAME,
+            Messages.COLUMN_PID,
+        };
+
+        private List<LocalVirtualMachine> vmList;
+
+        public int getColumnCount() {
+            return columnNames.length;
+        }
+
+        public String getColumnName(int col) {
+            return columnNames[col];
+        }
+
+        public synchronized int getRowCount() {
+            return vmList.size();
+        }
+
+        public synchronized Object getValueAt(int row, int col) {
+            assert col >= 0 && col <= columnNames.length;
+            LocalVirtualMachine vm = vmList.get(row);
+            switch (col) {
+                case COL_NAME: return vm.displayName();
+                case COL_PID:  return vm.vmid();
+                default: return null;
+            }
+        }
+
+        public Class<?> getColumnClass(int column) {
+            switch (column) {
+                case COL_NAME: return String.class;
+                case COL_PID:  return Integer.class;
+                default: return super.getColumnClass(column);
+            }
+        }
+
+        public ManagedVmTableModel() {
+            refresh();
+        }
+
+
+        public synchronized LocalVirtualMachine vmAt(int pos) {
+            return vmList.get(pos);
+        }
+
+        public synchronized void refresh() {
+            Map<Integer, LocalVirtualMachine> map =
+                LocalVirtualMachine.getAllVirtualMachines();
+            vmList = new ArrayList<LocalVirtualMachine>();
+            vmList.addAll(map.values());
+
+            // data has changed
+            fireTableDataChanged();
+        }
+    }
+
+    // A blank component that takes up as much space as the
+    // button part of a JRadioButton.
+    private static class Padder extends JPanel {
+        JRadioButton radioButton;
+
+        Padder(JRadioButton radioButton) {
+            this.radioButton = radioButton;
+
+            setAccessibleName(this, Messages.BLANK);
+        }
+
+        public Dimension getPreferredSize() {
+            Rectangle r = getTextRectangle(radioButton);
+            int w = (r != null && r.x > 8) ? r.x : 22;
+
+            return new Dimension(w, 0);
+        }
+
+        private static Rectangle getTextRectangle(AbstractButton button) {
+            String text = button.getText();
+            Icon icon = (button.isEnabled()) ? button.getIcon() : button.getDisabledIcon();
+
+            if (icon == null && button.getUI() instanceof BasicRadioButtonUI) {
+                icon = ((BasicRadioButtonUI)button.getUI()).getDefaultIcon();
+            }
+
+            if ((icon == null) && (text == null)) {
+                return null;
+            }
+
+            Rectangle paintIconR = new Rectangle();
+            Rectangle paintTextR = new Rectangle();
+            Rectangle paintViewR = new Rectangle();
+            Insets paintViewInsets = new Insets(0, 0, 0, 0);
+
+            paintViewInsets = button.getInsets(paintViewInsets);
+            paintViewR.x = paintViewInsets.left;
+            paintViewR.y = paintViewInsets.top;
+            paintViewR.width = button.getWidth() - (paintViewInsets.left + paintViewInsets.right);
+            paintViewR.height = button.getHeight() - (paintViewInsets.top + paintViewInsets.bottom);
+
+            Graphics g = button.getGraphics();
+            if (g == null) {
+                return null;
+            }
+                SwingUtilities.layoutCompoundLabel(button,
+                                                   g.getFontMetrics(),
+                                                   text,
+                                                   icon,
+                                                   button.getVerticalAlignment(),
+                                                   button.getHorizontalAlignment(),
+                                                   button.getVerticalTextPosition(),
+                                                   button.getHorizontalTextPosition(),
+                                                   paintViewR,
+                                                   paintIconR,
+                                                   paintTextR,
+                                                   button.getIconTextGap());
+
+            return paintTextR;
+        }
+    }
+
+}