Summary

JTreeにマウスカーソル下の行をロールオーバー描画する機能を追加し、JPopupMenuが表示されている場合はそのロールオーバー状態を維持するよう設定します。

Source Code Examples

class FileSystemViewTree extends JTree {
  private static final Color SELECTED_COLOR = new Color(0x00_78_D7);
  private static final Color ROLLOVER_COLOR = new Color(0x64_96_C8);
  protected int rollOverRowIndex = -1;
  protected transient MouseAdapter rolloverHandler;
  private transient FileSystemView fileSystemView;

  protected FileSystemViewTree() {
    super();
  }

  @Override protected void paintComponent(Graphics g) {
    int[] sr = getSelectionRows();
    if (sr == null) {
      super.paintComponent(g);
      return;
    }
    g.setColor(getBackground());
    g.fillRect(0, 0, getWidth(), getHeight());
    Graphics2D g2 = (Graphics2D) g.create();
    if (rollOverRowIndex >= 0) {
      g2.setPaint(ROLLOVER_COLOR);
      Rectangle rect = getRowBounds(rollOverRowIndex);
      g2.fillRect(0, rect.y, getWidth(), rect.height);
    }
    g2.setPaint(SELECTED_COLOR);
    Arrays.stream(sr).mapToObj(this::getRowBounds)
        .forEach(r -> g2.fillRect(0, r.y, getWidth(), r.height));
    super.paintComponent(g);
    if (hasFocus()) {
      Optional.ofNullable(getLeadSelectionPath()).ifPresent(path -> {
        Rectangle r = getRowBounds(getRowForPath(path));
        g2.setPaint(SELECTED_COLOR.darker());
        g2.drawRect(0, r.y, getWidth() - 1, r.height - 1);
      });
    }
    g2.dispose();
  }

  @Override public void updateUI() {
    setCellRenderer(null);
    removeMouseListener(rolloverHandler);
    removeMouseMotionListener(rolloverHandler);
    super.updateUI();
    setUI(new WholeRowSelectTreeUI());
    UIManager.put("Tree.repaintWholeRow", Boolean.TRUE);
    fileSystemView = FileSystemView.getFileSystemView();
    addTreeSelectionListener(new FolderSelectionListener(fileSystemView));
    DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
    setCellRenderer((tree, value, selected, expanded, leaf, row, focus) -> {
      Component c = renderer.getTreeCellRendererComponent(
          tree, value, selected, expanded, leaf, row, false);
      boolean rollover = row == rollOverRowIndex;
      updateFgc(c, renderer.getTextSelectionColor(), rollover);
      updateBgc(c, tree.getBackground(), selected, rollover);
      updateIcon(c, value, selected);
      return c;
    });
    setOpaque(false);
    rolloverHandler = new RolloverHandler();
    addMouseListener(rolloverHandler);
    addMouseMotionListener(rolloverHandler);
    EventQueue.invokeLater(() -> setModel(makeFileTreeModel(fileSystemView)));
  }

  public void updateRolloverIndex() {
    EventQueue.invokeLater(() -> {
      Point pt = getMousePosition();
      if (pt == null) {
        clearRollover();
      } else {
        updateRolloverIndex(pt);
      }
    });
  }

  public void updateRolloverIndex(Point pt) {
    int row = getRowForLocation(pt.x, pt.y);
    boolean isPopupVisible = getComponentPopupMenu().isVisible();
    if (rollOverRowIndex != row && !isPopupVisible) {
      rollOverRowIndex = row;
      repaint();
    }
  }

  private void clearRollover() {
    rollOverRowIndex = -1;
    repaint();
  }

  protected class RolloverHandler extends MouseAdapter {
    @Override public void mouseMoved(MouseEvent e) {
      updateRolloverIndex(e.getPoint());
    }

    @Override public void mouseEntered(MouseEvent e) {
      updateRolloverIndex(e.getPoint());
    }

    @Override public void mouseExited(MouseEvent e) {
      boolean isPopupVisible = getComponentPopupMenu().isVisible();
      if (!isPopupVisible) {
        clearRollover();
      }
    }
  }
}
View in GitHub: Java, Kotlin

Explanation

  • JTreeMouseAdapterを追加し、マウスカーソルが移動したらJTree#getRowForLocation(x, y)メソッドでマウスカーソル下の行番号を記憶し、その行の文字色はセルレンダラー、背景色はJTree#paintComponent(...)をオーバーライドして変更
    • デフォルトではJTree#setComponentPopupMenu(JPopupMenu)で設定したJPopupMenuが表示されるとmouseExitedイベントが発生したり、そのJPopupMenuが表示中でもJTree上にマウスカーソルが移動するとmouseMovedイベントが発生してロールオーバー状態の行番号が変化してしまう
    • このサンプルではマウスカーソル移動時にJPopupMenuが表示中出ない場合のみロールオーバー状態の行番号を変更するよう設定してこれを回避
    • さらにJPopupMenu側にはPopupMenuListenerを追加し、JPopupMenuが非表示になる際にマウスカーソル位置をJTree#getMousePosition()で取得してロールオーバー状態の行番号を更新する
      • JTree#getMousePosition()で取得したPointnullの場合、マウスカーソルはJTreeの外に存在するのでロールオーバー状態はクリアする

Reference

Comment