// {{{ InstallPanel constructor
  InstallPanel(PluginManager window, boolean updates) {
    super(new BorderLayout(12, 12));

    this.window = window;
    this.updates = updates;

    setBorder(new EmptyBorder(12, 12, 12, 12));

    final JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
    split.setResizeWeight(0.75);
    /* Setup the table */
    table = new JTable(pluginModel = new PluginTableModel());
    table.setShowGrid(false);
    table.setIntercellSpacing(new Dimension(0, 0));
    table.setRowHeight(table.getRowHeight() + 2);
    table.setPreferredScrollableViewportSize(new Dimension(500, 200));
    table.setDefaultRenderer(
        Object.class,
        new TextRenderer((DefaultTableCellRenderer) table.getDefaultRenderer(Object.class)));
    table.addFocusListener(new TableFocusHandler());
    InputMap tableInputMap = table.getInputMap(JComponent.WHEN_FOCUSED);
    ActionMap tableActionMap = table.getActionMap();
    tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "tabOutForward");
    tableActionMap.put("tabOutForward", new KeyboardAction(KeyboardCommand.TAB_OUT_FORWARD));
    tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK), "tabOutBack");
    tableActionMap.put("tabOutBack", new KeyboardAction(KeyboardCommand.TAB_OUT_BACK));
    tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "editPlugin");
    tableActionMap.put("editPlugin", new KeyboardAction(KeyboardCommand.EDIT_PLUGIN));
    tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "closePluginManager");
    tableActionMap.put(
        "closePluginManager", new KeyboardAction(KeyboardCommand.CLOSE_PLUGIN_MANAGER));

    TableColumn col1 = table.getColumnModel().getColumn(0);
    TableColumn col2 = table.getColumnModel().getColumn(1);
    TableColumn col3 = table.getColumnModel().getColumn(2);
    TableColumn col4 = table.getColumnModel().getColumn(3);
    TableColumn col5 = table.getColumnModel().getColumn(4);

    col1.setPreferredWidth(30);
    col1.setMinWidth(30);
    col1.setMaxWidth(30);
    col1.setResizable(false);

    col2.setPreferredWidth(180);
    col3.setPreferredWidth(130);
    col4.setPreferredWidth(70);
    col5.setPreferredWidth(70);

    JTableHeader header = table.getTableHeader();
    header.setReorderingAllowed(false);
    header.addMouseListener(new HeaderMouseHandler());
    header.setDefaultRenderer(
        new HeaderRenderer((DefaultTableCellRenderer) header.getDefaultRenderer()));

    scrollpane = new JScrollPane(table);
    scrollpane.getViewport().setBackground(table.getBackground());
    split.setTopComponent(scrollpane);

    /* Create description */
    JScrollPane infoPane = new JScrollPane(infoBox = new PluginInfoBox());
    infoPane.setPreferredSize(new Dimension(500, 100));
    split.setBottomComponent(infoPane);

    EventQueue.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            split.setDividerLocation(0.75);
          }
        });

    final JTextField searchField = new JTextField();
    searchField.addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP) {
              table.dispatchEvent(e);
              table.requestFocus();
            }
          }
        });
    searchField
        .getDocument()
        .addDocumentListener(
            new DocumentListener() {
              void update() {
                pluginModel.setFilterString(searchField.getText());
              }

              @Override
              public void changedUpdate(DocumentEvent e) {
                update();
              }

              @Override
              public void insertUpdate(DocumentEvent e) {
                update();
              }

              @Override
              public void removeUpdate(DocumentEvent e) {
                update();
              }
            });
    table.addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            int i = table.getSelectedRow(), n = table.getModel().getRowCount();
            if (e.getKeyCode() == KeyEvent.VK_DOWN && i == (n - 1)
                || e.getKeyCode() == KeyEvent.VK_UP && i == 0) {
              searchField.requestFocus();
              searchField.selectAll();
            }
          }
        });
    Box filterBox = Box.createHorizontalBox();
    filterBox.add(new JLabel("Filter : "));
    filterBox.add(searchField);
    add(BorderLayout.NORTH, filterBox);
    add(BorderLayout.CENTER, split);

    /* Create buttons */
    Box buttons = new Box(BoxLayout.X_AXIS);

    buttons.add(new InstallButton());
    buttons.add(Box.createHorizontalStrut(12));
    buttons.add(new SelectallButton());
    buttons.add(chooseButton = new ChoosePluginSet());
    buttons.add(new ClearPluginSet());
    buttons.add(Box.createGlue());
    buttons.add(new SizeLabel());

    add(BorderLayout.SOUTH, buttons);
    String path = jEdit.getProperty(PluginManager.PROPERTY_PLUGINSET, "");
    if (!path.isEmpty()) {
      loadPluginSet(path);
    }
  } // }}}
  public MemoryPanel(final Debugger debugger, boolean is64Bit) {
    super();
    this.debugger = debugger;
    this.is64Bit = is64Bit;
    if (is64Bit) {
      addressSize = 8;
      unmappedAddrString = "??????????????????";
    } else {
      addressSize = 4;
      unmappedAddrString = "??????????";
    }
    setLayout(new BorderLayout());
    setupScrollBar();
    add(scrollBar, BorderLayout.EAST);

    model =
        new AbstractTableModel() {
          public int getRowCount() {
            return numVisibleRows;
          }

          public int getColumnCount() {
            return 2;
          }

          public Object getValueAt(int row, int column) {
            switch (column) {
              case 0:
                return bigIntToHexString(
                    startVal.add(new BigInteger(Integer.toString((row * addressSize)))));
              case 1:
                {
                  try {
                    Address addr =
                        bigIntToAddress(
                            startVal.add(new BigInteger(Integer.toString((row * addressSize)))));
                    if (addr != null) {
                      return addressToString(addr.getAddressAt(0));
                    }
                    return unmappedAddrString;
                  } catch (UnmappedAddressException e) {
                    return unmappedAddrString;
                  }
                }
              default:
                throw new RuntimeException("Column " + column + " out of bounds");
            }
          }

          public boolean isCellEditable(int row, int col) {
            return false;
          }
        };

    // View with JTable with no header
    table = new JTable(model);
    table.setTableHeader(null);
    table.setShowGrid(false);
    table.setIntercellSpacing(new Dimension(0, 0));
    table.setCellSelectionEnabled(true);
    table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    table.setDragEnabled(true);
    Font font = GraphicsUtilities.lookupFont("Courier");
    if (font == null) {
      throw new RuntimeException("Error looking up monospace font Courier");
    }
    table.setFont(font);

    // Export proper data.
    // We need to keep our own notion of the selection in order to
    // properly export data, since the selection can go beyond the
    // visible area on the screen (and since the table's model doesn't
    // back all of those slots).
    // Code thanks to Shannon.Hickey@sfbay
    table.setTransferHandler(
        new TransferHandler() {
          protected Transferable createTransferable(JComponent c) {
            JTable table = (JTable) c;
            if (haveSelection()) {
              StringBuffer buf = new StringBuffer();
              int iDir = (getRowAnchor() < getRowLead() ? 1 : -1);
              int jDir = (getColAnchor() < getColLead() ? 1 : -1);

              for (int i = getRowAnchor(); i != getRowLead() + iDir; i += iDir) {
                for (int j = getColAnchor(); j != getColLead() + jDir; j += jDir) {
                  Object val = model.getValueAt(i, j);
                  buf.append(val == null ? "" : val.toString());
                  if (j != getColLead()) {
                    buf.append("\t");
                  }
                }
                if (i != getRowLead()) {
                  buf.append("\n");
                }
              }

              return new StringTransferable(buf.toString());
            }
            return null;
          }

          public int getSourceActions(JComponent c) {
            return COPY;
          }

          public boolean importData(JComponent c, Transferable t) {
            if (canImport(c, t.getTransferDataFlavors())) {
              try {
                String str = (String) t.getTransferData(DataFlavor.stringFlavor);
                handleImport(c, str);
                return true;
              } catch (UnsupportedFlavorException ufe) {
              } catch (IOException ioe) {
              }
            }

            return false;
          }

          public boolean canImport(JComponent c, DataFlavor[] flavors) {
            for (int i = 0; i < flavors.length; i++) {
              if (DataFlavor.stringFlavor.equals(flavors[i])) {
                return true;
              }
            }
            return false;
          }

          private void handleImport(JComponent c, String str) {
            // do whatever you want with the string here
            try {
              makeVisible(debugger.parseAddress(str));
              clearSelection();
              table.clearSelection();
            } catch (NumberFormatException e) {
              System.err.println("Unable to parse address \"" + str + "\"");
            }
          }
        });

    // Supporting keyboard scrolling
    // See src/share/classes/javax/swing/plaf/metal/MetalLookAndFeel.java,
    // search for Table.AncestorInputMap

    // Actions to override:
    // selectPreviousRow, selectNextRow,
    // scrollUpChangeSelection, scrollDownChangeSelection,
    // selectPreviousRowExtendSelection, selectNextRowExtendSelection,
    // scrollDownExtendSelection, scrollUpExtendSelection (Shift-PgDn/PgUp)

    ActionMap map = table.getActionMap();

    // Up arrow
    installActionWrapper(
        map,
        "selectPreviousRow",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            clearSelection();
            if (table.getSelectedRow() == 0) {
              scrollBar.scrollUpOrLeft();
              table.setRowSelectionInterval(0, 0);
            } else {
              super.actionPerformed(e);
            }
            maybeGrabSelection();
            endUpdate();
          }
        });
    // Down arrow
    installActionWrapper(
        map,
        "selectNextRow",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            clearSelection();
            int row = table.getSelectedRow();
            if (row >= numUsableRows) {
              scrollBar.scrollDownOrRight();
              table.setRowSelectionInterval(row, row);
            } else {
              super.actionPerformed(e);
            }
            maybeGrabSelection();
            endUpdate();
          }
        });
    // Page up
    installActionWrapper(
        map,
        "scrollUpChangeSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            clearSelection();
            int row = table.getSelectedRow();
            scrollBar.pageUpOrLeft();
            if (row >= 0) {
              table.setRowSelectionInterval(row, row);
            }
            maybeGrabSelection();
            endUpdate();
          }
        });
    // Page down
    installActionWrapper(
        map,
        "scrollDownChangeSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            clearSelection();
            int row = table.getSelectedRow();
            scrollBar.pageDownOrRight();
            if (row >= 0) {
              table.setRowSelectionInterval(row, row);
            }
            maybeGrabSelection();
            endUpdate();
          }
        });
    // Shift + Up arrow
    installActionWrapper(
        map,
        "selectPreviousRowExtendSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            if (!haveAnchor()) {
              setAnchorFromTable();
              setLeadFromTable();
              //            setAnchor(table.getSelectedRow());
              //            setLead(table.getSelectedRow());
            }
            int newLead = getRowLead() - 1;
            int newAnchor = getRowAnchor();
            if (newLead < 0) {
              scrollBar.scrollUpOrLeft();
              ++newLead;
              ++newAnchor;
            }
            setSelection(newAnchor, newLead, getColAnchor(), getColLead());
            //          printSelection();
            endUpdate();
          }
        });
    // Shift + Left arrow
    installActionWrapper(
        map,
        "selectPreviousColumnExtendSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            if (!haveAnchor()) {
              setAnchorFromTable();
              setLeadFromTable();
            }
            int newLead = Math.max(0, getColLead() - 1);
            setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead);
            //          printSelection();
            endUpdate();
          }
        });
    // Shift + Down arrow
    installActionWrapper(
        map,
        "selectNextRowExtendSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            if (!haveAnchor()) {
              setAnchorFromTable();
              setLeadFromTable();
              //            setAnchor(table.getSelectedRow());
              //            setLead(table.getSelectedRow());
            }
            int newLead = getRowLead() + 1;
            int newAnchor = getRowAnchor();
            if (newLead > numUsableRows) {
              scrollBar.scrollDownOrRight();
              --newLead;
              --newAnchor;
            }
            setSelection(newAnchor, newLead, getColAnchor(), getColLead());
            //          printSelection();
            endUpdate();
          }
        });
    // Shift + Right arrow
    installActionWrapper(
        map,
        "selectNextColumnExtendSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            if (!haveAnchor()) {
              setAnchorFromTable();
              setLeadFromTable();
            }
            int newLead = Math.min(model.getColumnCount() - 1, getColLead() + 1);
            setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead);
            //          printSelection();
            endUpdate();
          }
        });
    // Shift + Page up
    installActionWrapper(
        map,
        "scrollUpExtendSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            if (!haveAnchor()) {
              setAnchorFromTable();
              setLeadFromTable();
              //            setAnchor(table.getSelectedRow());
              //            setLead(table.getSelectedRow());
            }
            int newLead = getRowLead() - numUsableRows;
            int newAnchor = getRowAnchor();
            if (newLead < 0) {
              scrollBar.pageUpOrLeft();
              newLead += numUsableRows;
              newAnchor += numUsableRows;
            }
            setSelection(newAnchor, newLead, getColAnchor(), getColLead());
            //          printSelection();
            endUpdate();
          }
        });
    // Shift + Page down
    installActionWrapper(
        map,
        "scrollDownExtendSelection",
        new ActionWrapper() {
          public void actionPerformed(ActionEvent e) {
            beginUpdate();
            if (!haveAnchor()) {
              setAnchorFromTable();
              setLeadFromTable();
              //            setAnchor(table.getSelectedRow());
              //            setLead(table.getSelectedRow());
            }
            int newLead = getRowLead() + numUsableRows;
            int newAnchor = getRowAnchor();
            if (newLead > numUsableRows) {
              scrollBar.pageDownOrRight();
              newLead -= numUsableRows;
              newAnchor -= numUsableRows;
            }
            setSelection(newAnchor, newLead, getColAnchor(), getColLead());
            //          printSelection();
            endUpdate();
          }
        });

    // Clear our notion of selection upon mouse press
    table.addMouseListener(
        new MouseAdapter() {
          public void mousePressed(MouseEvent e) {
            if (shouldIgnore(e)) {
              return;
            }
            // Make shift-clicking work properly
            if (e.isShiftDown()) {
              maybeGrabSelection();
              return;
            }
            //          System.err.println("  Clearing selection on mouse press");
            clearSelection();
          }
        });

    // Watch for mouse going out of bounds
    table.addMouseMotionListener(
        new MouseMotionAdapter() {
          public void mouseDragged(MouseEvent e) {
            if (shouldIgnore(e)) {
              //            System.err.println("  (Ignoring consumed mouse event)");
              return;
            }

            // Look for drag events outside table and scroll if necessary
            Point p = e.getPoint();
            if (table.rowAtPoint(p) == -1) {
              // See whether we are above or below the table
              Rectangle rect = new Rectangle();
              getBounds(rect);
              beginUpdate();
              if (p.y < rect.y) {
                //              System.err.println("  Scrolling up due to mouse event");
                // Scroll up
                scrollBar.scrollUpOrLeft();
                setSelection(getRowAnchor(), 0, getColAnchor(), getColLead());
              } else {
                //              System.err.println("  Scrolling down due to mouse event");
                // Scroll down
                scrollBar.scrollDownOrRight();
                setSelection(getRowAnchor(), numUsableRows, getColAnchor(), getColLead());
              }
              //            printSelection();
              endUpdate();
            } else {
              maybeGrabSelection();
            }
          }
        });

    add(table, BorderLayout.CENTER);

    // Make sure we recompute number of visible rows
    addComponentListener(
        new ComponentAdapter() {
          public void componentResized(ComponentEvent e) {
            recomputeNumVisibleRows();
            constrain();
          }
        });
    addHierarchyListener(
        new HierarchyListener() {
          public void hierarchyChanged(HierarchyEvent e) {
            recomputeNumVisibleRows();
            constrain();
          }
        });
    updateFromScrollBar();
  }