Google Tag Manager

Showing posts with label JScrollPane. Show all posts
Showing posts with label JScrollPane. Show all posts

2025/02/28

Display the insert cursor for a new column on the boundary of a JTable column

Code

class ColumnInsertLayerUI extends LayerUI<JScrollPane> {
  private static final Color LINE_COLOR = new Color(0x00_78_D7);
  private static final int LINE_WIDTH = 4;
  private final Rectangle2D line = new Rectangle2D.Double();
  private final Ellipse2D plus = new Ellipse2D.Double(0d, 0d, 10d, 10d);

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer && !line.isEmpty()) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
      JTableHeader header =
          ((JTable) scroll.getViewport().getView()).getTableHeader();
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      Point pt0 = line.getBounds().getLocation();
      Point pt1 = SwingUtilities.convertPoint(header, pt0, c);
      g2.translate(pt1.getX() - pt0.getX(), pt1.getY() - pt0.getY());
      // paint Insert Line
      g2.setPaint(LINE_COLOR);
      g2.fill(line);
      // paint Plus Icon
      g2.setPaint(Color.WHITE);
      g2.fill(plus);
      g2.setPaint(LINE_COLOR);
      double cx = plus.getCenterX();
      double cy = plus.getCenterY();
      double w2 = plus.getWidth() / 2d;
      double h2 = plus.getHeight() / 2d;
      g2.draw(new Line2D.Double(cx - w2, cy, cx + w2, cy));
      g2.draw(new Line2D.Double(cx, cy - h2, cx, cy + h2));
      g2.draw(plus);
      g2.dispose();
    }
  }

  private void updateLineLocation(JScrollPane scroll, Point loc) {
    JTable table = (JTable) scroll.getViewport().getView();
    JTableHeader header = table.getTableHeader();
    Rectangle rect = scroll.getVisibleRect();
    JScrollBar bar = scroll.getHorizontalScrollBar();
    int scrollHeight = bar.isVisible() ? bar.getHeight() : 0;
    Dimension d = new Dimension(
        LINE_WIDTH, rect.height - scrollHeight);
    for (int i = 0; i < table.getColumnCount(); i++) {
      if (canInsert(header, loc, i, d)) {
        return;
      }
    }
  }

  private boolean canInsert(
        JTableHeader header, Point loc, int i, Dimension d) {
    Rectangle r = header.getHeaderRect(i);
    Rectangle r1 = getWestRect(r, i);
    Rectangle r2 = getEastRect(r);
    boolean hit = false;
    if (r1.contains(loc)) {
      updateInsertLineLocation(r1, loc, d, header);
      hit = true;
    } else if (r2.contains(loc)) {
      updateInsertLineLocation(r2, loc, d, header);
      hit = true;
    } else if (r.contains(loc)) {
      line.setFrame(0d, 0d, 0d, 0d);
      header.setCursor(Cursor.getDefaultCursor());
      hit = true;
    }
    return hit;
  }

  private Rectangle getWestRect(Rectangle r, int i) {
    Rectangle rect = r.getBounds();
    Rectangle bounds = plus.getBounds();
    if (i != 0) {
      rect.x -= bounds.width / 2;
    }
    rect.setSize(bounds.getSize());
    return rect;
  }

  private Rectangle getEastRect(Rectangle r) {
    Rectangle rect = r.getBounds();
    Rectangle bounds = plus.getBounds();
    rect.x += rect.width - bounds.width / 2;
    rect.setSize(bounds.getSize());
    return rect;
  }

  private void updateInsertLineLocation(
        Rectangle r, Point loc, Dimension d, Component c) {
    if (r.contains(loc)) {
      double cx = r.getCenterX();
      double cy = r.getCenterY();
      line.setFrame(
          cx - d.getWidth() / 2d, r.getY(), d.width, d.height);
      double pw = plus.getWidth() / 2d;
      double ph = plus.getHeight() / 2d;
      plus.setFrameFromCenter(cx, cy, cx - pw, cy - ph);
      c.setCursor(Cursor.getDefaultCursor());
    } else {
      line.setFrame(0d, 0d, 0d, 0d);
      c.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
    }
  }

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseEvent(
        MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseEvent(e, l);
    if (e.getID() == MouseEvent.MOUSE_CLICKED) {
      JScrollPane scroll = l.getView();
      Point pt = e.getPoint();
      if (plus.contains(pt) && !line.isEmpty()) {
        JTable table = (JTable) scroll.getViewport().getView();
        TableModel model = table.getModel();
        int columnCount = table.getColumnCount();
        int maxColumn = model.getColumnCount();
        if (columnCount < maxColumn) {
          int idx = table.columnAtPoint(
              line.getBounds().getLocation());
          TableColumn column = new TableColumn(columnCount);
          column.setHeaderValue("Column" + columnCount);
          table.addColumn(column);
          table.moveColumn(columnCount, idx + 1);
          updateLineLocation(scroll, pt);
        }
      }
      l.repaint(scroll.getBounds());
    }
  }

  @Override protected void processMouseMotionEvent(
        MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseMotionEvent(e, l);
    Component c = e.getComponent();
    int id = e.getID();
    JScrollPane scroll = l.getView();
    if (id == MouseEvent.MOUSE_MOVED && c instanceof JTableHeader) {
      updateLineLocation(scroll, e.getPoint());
    } else {
      line.setFrame(0d, 0d, 0d, 0d);
    }
    l.repaint(scroll.getBounds());
  }
}

References

2025/01/31

Limit the area where TableColumns can be reordered by dragging

Limit the area where JTableHeader column reordering drags can be initiated to the top half of the TableColumn and perform mouse cursor changes and draw drag handle icons on the JLayer.

Code

class ColumnDragLayerUI extends LayerUI<JScrollPane> {
  private final Rectangle draggableRect = new Rectangle();

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (!draggableRect.isEmpty()) {
      Graphics2D g2 = (Graphics2D) g.create();
      // g2.fill(draggableRect);
      Icon icon = new DragAreaIcon();
      int x = (int) (draggableRect.getCenterX() - icon.getIconWidth() / 2d);
      int y = draggableRect.y + 1;
      icon.paintIcon(c, g2, x, y);
      g2.dispose();
    }
  }

  @Override protected void processMouseEvent(
      MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseEvent(e, l);
    Component c = e.getComponent();
    if (c instanceof JTableHeader) {
      JTableHeader header = (JTableHeader) c;
      if (e.getID() == MouseEvent.MOUSE_PRESSED) {
        Point pt = e.getPoint();
        updateIconAndCursor(header, pt, l);
      } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
        header.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        draggableRect.setSize(0, 0);
      }
    }
  }

  @Override protected void processMouseMotionEvent(
      MouseEvent e, JLayer<? extends JScrollPane> l) {
    Component c = e.getComponent();
    if (c instanceof JTableHeader) {
      JTableHeader header = (JTableHeader) c;
      if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
        TableColumn draggedColumn = header.getDraggedColumn();
        if (!draggableRect.isEmpty() && draggedColumn != null) {
          EventQueue.invokeLater(() -> {
            int modelIndex = draggedColumn.getModelIndex();
            int viewIndex = header.getTable().convertColumnIndexToView(modelIndex);
            Rectangle rect = header.getHeaderRect(viewIndex);
            rect.x += header.getDraggedDistance();
            draggableRect.setRect(SwingUtilities.convertRectangle(header, rect, l));
            header.repaint(rect);
          });
        } else {
          e.consume(); // Refuse to start drag
        }
      } else if (e.getID() == MouseEvent.MOUSE_MOVED) {
        Point pt = e.getPoint();
        updateIconAndCursor(header, pt, l);
        header.repaint();
      }
    } else {
      c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      draggableRect.setSize(0, 0);
    }
  }

  private void updateIconAndCursor(JTableHeader header, Point pt, JLayer<?> l) {
    Rectangle r = header.getHeaderRect(header.columnAtPoint(pt));
    r.height /= 2;
    if (r.contains(pt)) {
      header.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      draggableRect.setRect(SwingUtilities.convertRectangle(header, r, l));
    } else {
      header.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      draggableRect.setSize(0, 0);
    }
  }
}

References

2024/12/01

Implement sticky header in JList

Code

class StickyLayerUI extends LayerUI<JScrollPane> {
  private final JPanel renderer = new JPanel();
  private int currentHeaderIdx = -1;
  private int nextHeaderIdx = -1;

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_WHEEL_EVENT_MASK
          | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseMotionEvent(
      MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseMotionEvent(e, l);
    Component c = l.getView().getViewport().getView();
    if (e.getID() == MouseEvent.MOUSE_DRAGGED && c instanceof JList) {
      update((JList<?>) c);
    }
  }

  @Override protected void processMouseWheelEvent(
      MouseWheelEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseWheelEvent(e, l);
    Component c = l.getView().getViewport().getView();
    if (c instanceof JList) {
      update((JList<?>) c);
    }
  }

  private void update(JList<?> list) {
    int idx = list.getFirstVisibleIndex();
    if (idx >= 0) {
      currentHeaderIdx = getHeaderIndex1(list, idx);
      nextHeaderIdx = getNextHeaderIndex1(list, idx);
    } else {
      currentHeaderIdx = -1;
      nextHeaderIdx = -1;
    }
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    JList<?> list = getList(c);
    if (list != null && currentHeaderIdx >= 0) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
      Rectangle headerRect = scroll.getViewport().getBounds();
      headerRect.height = list.getFixedCellHeight();
        Graphics2D g2 = (Graphics2D) g.create();
      int firstVisibleIdx = list.getFirstVisibleIndex();
      if (firstVisibleIdx + 1 == nextHeaderIdx) {
        Dimension d = headerRect.getSize();
        Component c1 = getComponent(list, currentHeaderIdx);
        Rectangle r1 = getHeaderRect(list, firstVisibleIdx, c, d);
        SwingUtilities.paintComponent(g2, c1, renderer, r1);
        Component c2 = getComponent(list, nextHeaderIdx);
        Rectangle r2 = getHeaderRect(list, nextHeaderIdx, c, d);
        SwingUtilities.paintComponent(g2, c2, renderer, r2);
        } else {
        Component c1 = getComponent(list, currentHeaderIdx);
        SwingUtilities.paintComponent(g2, c1, renderer, headerRect);
        }
        g2.dispose();
      }
    }

  private static JList<?> getList(JComponent layer) {
    JList<?> list = null;
    if (layer instanceof JLayer) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) layer).getView();
      Component view = scroll.getViewport().getView();
      if (view instanceof JList) {
        list = (JList<?>) view;
      }
    }
    return list;
  }

  private static int getHeaderIndex1(JList<?> list, int start) {
    return list.getNextMatch("0", start, Position.Bias.Backward);
  }

  private static int getNextHeaderIndex1(JList<?> list, int start) {
    return list.getNextMatch("0", start, Position.Bias.Forward);
  }

  private static Rectangle getHeaderRect(
      JList<?> list, int i, Component dst, Dimension d) {
    Rectangle r = SwingUtilities.convertRectangle(
        list, list.getCellBounds(i, i), dst);
    r.setSize(d);
    return r;
  }

  private static <E> Component getComponent(JList<E> list, int idx) {
    E value = list.getModel().getElementAt(idx);
    ListCellRenderer<? super E> r = list.getCellRenderer();
    Component c = r.getListCellRendererComponent(
        list, value, idx, false, false);
    c.setBackground(Color.GRAY);
    c.setForeground(Color.WHITE);
    return c;
  }
}

References

2022/09/30

Make the editable JTextArea scrollable beyond its last line

Code

// https://stackoverflow.com/questions/36715803/scrolling-past-the-end-in-idea
// IntelliJ IDEA: Settings -> Editor -> General -> Check Show virtual space at file bottom
JTextArea textArea = new JTextArea() {
  // https://stackoverflow.com/questions/32679335/java-jtextarea-allow-scrolling-beyond-end-of-text
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, this);
    if (c instanceof JScrollPane && isEditable()) {
      Rectangle r = ((JScrollPane) c).getViewportBorderBounds();
      d.height += r.height - getRowHeight() - getInsets().bottom;
    }
    return d;
  }
};

References

2022/07/31

use a newspaper-style JList that can scroll and display multiple items as a ComboBoxEditor

Code

ListModel<ListItem> model = makeModel();
JList<ListItem> list = new NewspaperStyleList<>(model);
JScrollPane scroll = new JScrollPane(list) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.width = 62 * 3 + 10;
    d.height = 32;
    return d;
  }
};
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
scroll.setBorder(BorderFactory.createEmptyBorder());
scroll.setViewportBorder(BorderFactory.createEmptyBorder());

JList<ListItem> list2 = new NewspaperStyleList<>(model);
JScrollPane scroll2 = new JScrollPane(list2) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.width = 62 * 4 + 10;
    return d;
  }
};
scroll2.setBorder(BorderFactory.createEmptyBorder());
scroll2.setViewportBorder(BorderFactory.createEmptyBorder());

JPopupMenu popup = new JPopupMenu();
popup.setLayout(new BorderLayout());
list2.addMouseListener(new MouseAdapter() {
  @Override public void mouseClicked(MouseEvent e) {
    if (popup.isVisible() && e.getClickCount() >= 2) {
      popup.setVisible(false);
    }
  }
});
popup.add(scroll2);
popup.setBorder(BorderFactory.createLineBorder(Color.GRAY));

JToggleButton dropDown = new JToggleButton(new DropDownArrowIcon());
popup.addPopupMenuListener(new PopupMenuListener() {
  @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    list2.setSelectedIndex(list.getSelectedIndex());
    EventQueue.invokeLater(popup::pack);
  }

  @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    dropDown.setSelected(false);
    list.requestFocusInWindow();
    int i = list2.getSelectedIndex();
    if (i >= 0) {
      list.setSelectedIndex(i);
      list.scrollRectToVisible(list.getCellBounds(i, i));
    }
  }

  @Override public void popupMenuCanceled(PopupMenuEvent e) {
    popupMenuWillBecomeInvisible(e);
  }
});

dropDown.setBorder(BorderFactory.createEmptyBorder());
dropDown.setContentAreaFilled(false);
dropDown.setFocusPainted(false);
dropDown.setFocusable(false);
dropDown.addItemListener(e -> {
  AbstractButton b = (AbstractButton) e.getItemSelectable();
  if (e.getStateChange() == ItemEvent.SELECTED) {
    popup.show(b, -scroll.getWidth(), b.getHeight());
  }
});

JScrollBar verticalScrollBar = scroll.getVerticalScrollBar();
JPanel verticalBox = new JPanel(new BorderLayout()) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 32 + 5 + 5;
    return d;
  }
};
verticalBox.setOpaque(false);
verticalBox.add(verticalScrollBar);
verticalBox.add(dropDown, BorderLayout.SOUTH);

JPanel panel = new JPanel(new BorderLayout(0, 0));
panel.add(scroll);
panel.add(verticalBox, BorderLayout.EAST);
panel.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));

References

2022/01/31

Fix the JToolTip display of JList cells to correspond to after mouse wheel rotation

Code

JList<String> list1 = new JList<String>(model) {
  @Override public void updateUI() {
    super.updateUI();
    setCellRenderer(new TooltipListCellRenderer<>());
  }
};
JScrollPane scroll1 = new JScrollPane(list1);
scroll1.addMouseWheelListener(e -> {
  JScrollPane scrollPane = (JScrollPane) e.getComponent();
  Component view = scrollPane.getViewport().getView();
  MouseEvent event = SwingUtilities.convertMouseEvent(scrollPane, e, view);
  ToolTipManager.sharedInstance().mouseMoved(event);
});

References

2021/10/01

Show a horizontal JScrollBar in the tab area of a JTabbedPane-like component created with CardLayout

Code

class CardLayoutTabbedPane extends JPanel {
  private final CardLayout cardLayout = new CardLayout();
  private final JPanel tabPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
  private final JPanel contentsPanel = new JPanel(cardLayout);
  private final JButton hiddenTabs = new JButton("V");
  private final ButtonGroup group = new ButtonGroup();
  private final JScrollPane tabArea = new JScrollPane(tabPanel) {
    @Override public boolean isOptimizedDrawingEnabled() {
      return false; // JScrollBar is overlap
    }

    @Override public void updateUI() {
      super.updateUI();
      EventQueue.invokeLater(() -> {
        getVerticalScrollBar().setUI(new OverlappedScrollBarUI());
        getHorizontalScrollBar().setUI(new OverlappedScrollBarUI());
        setLayout(new OverlapScrollPaneLayout());
        setComponentZOrder(getVerticalScrollBar(), 0);
        setComponentZOrder(getHorizontalScrollBar(), 1);
        setComponentZOrder(getViewport(), 2);
      });
      setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
      setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
      getVerticalScrollBar().setOpaque(false);
      getHorizontalScrollBar().setOpaque(false);
      setBackground(Color.DARK_GRAY);
      setViewportBorder(BorderFactory.createEmptyBorder());
      setBorder(BorderFactory.createEmptyBorder());
    }

    @Override public Dimension getPreferredSize() {
      Dimension d = super.getPreferredSize();
      d.height = 18 + 6;
      return d;
    }
  };

  protected CardLayoutTabbedPane() {
    super(new BorderLayout());
    setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
    setBackground(new Color(16, 16, 16));
    tabPanel.setInheritsPopupMenu(true);
    hiddenTabs.setFont(hiddenTabs.getFont().deriveFont(8f));
    hiddenTabs.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 8));
    hiddenTabs.setOpaque(false);
    hiddenTabs.setFocusable(false);
    hiddenTabs.setContentAreaFilled(false);
    JPanel header = new JPanel(new BorderLayout());
    header.add(new JLayer<>(tabArea, new HorizontalScrollLayerUI()));
    header.add(hiddenTabs, BorderLayout.EAST);
    add(header, BorderLayout.NORTH);
    add(contentsPanel);
  }

  protected JComponent createTabComponent(String title, Icon icon) {
    JToggleButton tab = new TabButton();
    tab.setInheritsPopupMenu(true);
    group.add(tab);
    tab.addMouseListener(new MouseAdapter() {
      @Override public void mousePressed(MouseEvent e) {
        if (SwingUtilities.isLeftMouseButton(e)) {
          ((AbstractButton) e.getComponent()).setSelected(true);
          cardLayout.show(contentsPanel, title);
        }
      }
    });
    EventQueue.invokeLater(() -> tab.setSelected(true));

    JLabel label = new JLabel(title, icon, SwingConstants.LEADING);
    label.setForeground(Color.WHITE);
    label.setIcon(icon);
    label.setOpaque(false);

    JButton close = new JButton(new CloseTabIcon(new Color(0xB0_B0_B0))) {
      @Override public Dimension getPreferredSize() {
        return new Dimension(12, 12);
      }
    };
    close.addActionListener(e -> System.out.println("close button"));
    close.setBorder(BorderFactory.createEmptyBorder());
    close.setFocusable(false);
    close.setOpaque(false);
    // close.setFocusPainted(false);
    close.setContentAreaFilled(false);
    close.setPressedIcon(new CloseTabIcon(new Color(0xFE_FE_FE)));
    close.setRolloverIcon(new CloseTabIcon(new Color(0xA0_A0_A0)));

    tab.add(label);
    tab.add(close, BorderLayout.EAST);
    return tab;
  }

  public void addTab(String title, Icon icon, Component comp) {
    JComponent tab = createTabComponent(title, icon);
    tabPanel.add(tab);
    contentsPanel.add(comp, title);
    cardLayout.show(contentsPanel, title);
    EventQueue.invokeLater(() -> tabPanel.scrollRectToVisible(tab.getBounds()));
  }

  public JScrollPane getTabArea() {
    return tabArea;
  }

  @Override public void doLayout() {
    BoundedRangeModel m = tabArea.getHorizontalScrollBar().getModel();
    hiddenTabs.setVisible(m.getMaximum() - m.getExtent() > 0);
    super.doLayout();
  }
}

References

2021/08/31

Add a JButton to the bottom right inside the JScrollPane to scroll back to the top area of the child component

Code

class ScrollBackToTopLayerUI extends LayerUI<JScrollPane> {
  private static final int GAP = 5;
  private final Container rubberStamp = new JPanel();
  private final Point mousePt = new Point();
  private final JButton button = new JButton(new ScrollBackToTopIcon()) {
    @Override public void updateUI() {
      super.updateUI();
      setBorder(BorderFactory.createEmptyBorder());
      setFocusPainted(false);
      setBorderPainted(false);
      setContentAreaFilled(false);
      setRolloverEnabled(false);
    }
  };
  private final Rectangle buttonRect = new Rectangle(button.getPreferredSize());

  private void updateButtonRect(JScrollPane scroll) {
    JViewport viewport = scroll.getViewport();
    int x = viewport.getX() + viewport.getWidth() - buttonRect.width - GAP;
    int y = viewport.getY() + viewport.getHeight() - buttonRect.height - GAP;
    buttonRect.setLocation(x, y);
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
      updateButtonRect(scroll);
      if (scroll.getViewport().getViewRect().y > 0) {
        button.getModel().setRollover(buttonRect.contains(mousePt));
        SwingUtilities.paintComponent(g, button, rubberStamp, buttonRect);
      }
    }
  }

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JScrollPane> l) {
    JScrollPane scroll = l.getView();
    Rectangle r = scroll.getViewport().getViewRect();
    Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), scroll);
    mousePt.setLocation(p);
    int id = e.getID();
    if (id == MouseEvent.MOUSE_CLICKED) {
      if (buttonRect.contains(mousePt)) {
        scrollBackToTop(l.getView());
      }
    } else if (id == MouseEvent.MOUSE_PRESSED && r.y > 0 && buttonRect.contains(mousePt)) {
      e.consume();
    }
  }

  @Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JScrollPane> l) {
    Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l.getView());
    mousePt.setLocation(p);
    l.repaint(buttonRect);
  }

  private void scrollBackToTop(JScrollPane scroll) {
    JComponent c = (JComponent) scroll.getViewport().getView();
    Rectangle current = scroll.getViewport().getViewRect();
    new Timer(20, e -> {
      Timer animator = (Timer) e.getSource();
      if (0 < current.y && animator.isRunning()) {
        current.y -= Math.max(1, current.y / 2);
        c.scrollRectToVisible(current);
      } else {
        animator.stop();
      }
    }).start();
  }
}

References

2020/01/30

Automatically update JTree node selection based on scroll position to indicate which link is currently active in the viewport of JEditorPane

Code

JScrollPane scroll = new JScrollPane(editor);
scroll.getVerticalScrollBar().getModel().addChangeListener(e -> {
  HTMLDocument.Iterator itr = doc.getIterator(HTML.Tag.A);
  for (; itr.isValid(); itr.next()) {
    try {
      Rectangle r = editor.modelToView(itr.getStartOffset());
      if (r != null && editor.getVisibleRect().contains(r.getLocation())) {
        searchTreeNode(tree, itr.getAttributes().getAttribute(HTML.Attribute.NAME));
        break;
      }
    } catch (BadLocationException ex) {
      UIManager.getLookAndFeel().provideErrorFeedback(editor);
    }
  }
});
// ...
tree.addTreeSelectionListener(e -> {
  if (!tree.isEnabled()) { // Ignore node selection from JEditorPane side
    return;
  }
  Object o = e.getNewLeadSelectionPath().getLastPathComponent();
  if (o instanceof DefaultMutableTreeNode) {
    DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
    String ref = Objects.toString(node.getUserObject());
    editor.scrollToReference(ref);
  }
});
// ...
private static void searchTreeNode(JTree tree, Object name) {
  TreeModel model = tree.getModel();
  DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
  Collections.list((Enumeration<?>) root.preorderEnumeration()).stream()
      .filter(DefaultMutableTreeNode.class::isInstance)
      .map(DefaultMutableTreeNode.class::cast)
      .filter(node -> Objects.equals(name, Objects.toString(node.getUserObject())))
      .findFirst()
      .ifPresent(node -> {
        tree.setEnabled(false); // Disable TreeSelectionListener in JTree
        TreePath path = new TreePath(node.getPath());
        tree.setSelectionPath(path);
        tree.scrollPathToVisible(path);
        tree.setEnabled(true); // Restores TreeSelectionListener in JTree
      });
}

References

2019/10/30

Wraps JPanel child components horizontally and dynamically expands their horizontal spacing

Code

class ScrollableWrapLayout extends FlowLayout {
  private final int fixedHorizontalGap;

  protected ScrollableWrapLayout(int align, int hgap, int vgap) {
    super(align, hgap, vgap);
    fixedHorizontalGap = hgap;
  }

  private int getPreferredHorizontalGap(Container target) {
    Insets insets = target.getInsets();
    int columns = 0;
    int width = target.getWidth();
    if (target.getParent() instanceof JViewport) {
      width = target.getParent().getBounds().width;
    }
    width -= insets.left + insets.right + fixedHorizontalGap * 2;
    for (int i = 0; i < target.getComponentCount(); i++) {
      Component m = target.getComponent(i);
      if (m.isVisible()) {
        Dimension d = m.getPreferredSize();
        if (width - d.width - fixedHorizontalGap < 0) {
          columns = i;
          break;
        }
        width -= d.width + fixedHorizontalGap;
      }
    }
    return fixedHorizontalGap + width / columns;
  }

  @Override public void layoutContainer(Container target) {
    setHgap(getPreferredHorizontalGap(target));
    super.layoutContainer(target);
  }

  @Override public Dimension preferredLayoutSize(Container target) {
    Dimension dim = super.preferredLayoutSize(target);
    synchronized (target.getTreeLock()) {
      if (target.getParent() instanceof JViewport) {
        dim.width = target.getParent().getBounds().width;
        for (int i = 0; i < target.getComponentCount(); i++) {
          Component m = target.getComponent(i);
          if (m.isVisible()) {
            Dimension d = m.getPreferredSize();
            dim.height = Math.max(dim.height, d.height + m.getY());
          }
        }
      }
      return dim;
    }
  }
}

References

2019/08/31

Create a minimap of JEditorPane with ImageIcon and overlay it on JScrollPane using LayoutManager

Code

private final JEditorPane editor = new JEditorPane();
private final JScrollPane scroll = new JScrollPane(editor);
private final JLabel label = new JLabel() {
  private transient MouseInputListener handler;
  @Override public void updateUI() {
    removeMouseListener(handler);
    removeMouseMotionListener(handler);
    super.updateUI();
    handler = new MiniMapHandler();
    addMouseListener(handler);
    addMouseMotionListener(handler);
  }

  @Override public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Container c = SwingUtilities.getAncestorOfClass(JViewport.class, editor);
    if (!(c instanceof JViewport) || editor == null) {
      return;
    }
    JViewport vport = (JViewport) c;
    Rectangle vrect = vport.getBounds(); // scroll.getViewportBorderBounds();
    Rectangle erect = editor.getBounds();
    Rectangle crect = SwingUtilities.calculateInnerArea(this, new Rectangle());

    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    double sy = crect.getHeight() / erect.getHeight();
    AffineTransform at = AffineTransform.getScaleInstance(1d, sy);

    // paint Thumb
    Rectangle thumbRect = new Rectangle(vrect);
    thumbRect.y = vport.getViewPosition().y;
    Rectangle r = at.createTransformedShape(thumbRect).getBounds();
    int y = crect.y + r.y;
    g2.setColor(THUMB_COLOR);
    g2.fillRect(0, y, crect.width, r.height);
    g2.setColor(THUMB_COLOR.darker());
    g2.drawRect(0, y, crect.width - 1, r.height - 1);
    g2.dispose();
  }
};

private class MiniMapHandler extends MouseInputAdapter {
  @Override public void mousePressed(MouseEvent e) {
    processMiniMapMouseEvent(e);
  }

  @Override public void mouseDragged(MouseEvent e) {
    processMiniMapMouseEvent(e);
  }

  protected final void processMiniMapMouseEvent(MouseEvent e) {
    Point pt = e.getPoint();
    Component c = (Component) e.getComponent();
    BoundedRangeModel m = scroll.getVerticalScrollBar().getModel();
    int brm = m.getMaximum() - m.getMinimum();
    int iv = (int) (.5 - m.getExtent() * .5 + pt.y * brm / (double) c.getHeight());
    m.setValue(iv);
  }
}
// ...
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll.getVerticalScrollBar().getModel().addChangeListener(e -> label.repaint());

JPanel pp = new JPanel(new BorderLayout(0, 0));
pp.add(label, BorderLayout.NORTH);
JScrollPane minimap = new JScrollPane(pp);
minimap.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
minimap.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

Box box = Box.createHorizontalBox();
box.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
box.add(Box.createHorizontalGlue());
box.add(button);

JPanel p = new JPanel() {
  @Override public boolean isOptimizedDrawingEnabled() {
    return false;
  }
};
p.setLayout(new BorderLayout(0, 0) {
  @Override public void layoutContainer(Container parent) {
    synchronized (parent.getTreeLock()) {
      Insets insets = parent.getInsets();
      int width = parent.getWidth();
      int height = parent.getHeight();
      int top = insets.top;
      int bottom = height - insets.bottom;
      int left = insets.left;
      int right = width - insets.right;
      Component ec = getLayoutComponent(parent, BorderLayout.EAST);
      if (Objects.nonNull(ec)) {
        Dimension d = ec.getPreferredSize();
        JScrollBar vsb = scroll.getVerticalScrollBar();
        int vsw = vsb.isVisible() ? vsb.getSize().width : 0;
        ec.setBounds(right - d.width - vsw, top, d.width, bottom - top);
      }
      Component cc = getLayoutComponent(parent, BorderLayout.CENTER);
      if (Objects.nonNull(cc)) {
        cc.setBounds(left, top, right - left, bottom - top);
      }
    }
  }
});
p.add(minimap, BorderLayout.EAST);
p.add(scroll);

References

2017/07/31

Detects that it has reached the bottom of JScrollPane

Code

JScrollPane scroll = new JScrollPane(c);
scroll.getVerticalScrollBar().getModel().addChangeListener(e -> {
  BoundedRangeModel m = (BoundedRangeModel) e.getSource();
  int extent = m.getExtent();
  int maximum = m.getMaximum();
  int value = m.getValue();
  if (value + extent >= maximum) {
    check.setEnabled(true);
  }
});

References

2016/03/31

How to hide the JTableHeader

Code

final JScrollPane scrollPane = new JScrollPane(table);
JCheckBox check = new JCheckBox("JTableHeader visible: ", true);
check.addActionListener(new ActionListener() {
  @Override public void actionPerformed(ActionEvent e) {
    JCheckBox cb = (JCheckBox) e.getSource();
    // table.getTableHeader().setVisible(cb.isSelected());
    scrollPane.getColumnHeader().setVisible(cb.isSelected());
    scrollPane.revalidate();
  }
});

References

2015/09/30

Adjust the height of every row in the JTable to fit the JViewport

Code

JTable table = new JTable(model) {
  int prevHeight = -1;
  int prevCount = -1;

  public void updateRowsHeigth(JViewport vport) {
    int height = vport.getExtentSize().height;
    int rowCount = getModel().getRowCount();
    int defautlRowHeight = height / rowCount;
    if ((height != prevHeight || rowCount != prevCount) && defautlRowHeight > 0) {
      int over = height - rowCount * defautlRowHeight;
      for (int i = 0; i < rowCount; i++) {
        int a = over-- > 0 ? i == rowCount - 1 ? over : 1 : 0;
        setRowHeight(i, defautlRowHeight + a);
      }
    }
    prevHeight = height;
    prevCount = rowCount;
  }

  @Override public void doLayout() {
    super.doLayout();
    Container p = SwingUtilities.getAncestorOfClass(JViewport.class, this);
    if (p instanceof JViewport) {
      updateRowsHeigth((JViewport) p);
    }
  }
};

References

2015/06/30

An Image inside a JScrollPane zooming by mouse wheel and panning by mouse click to drag

Code

class ZoomAndPanePanel extends JPanel {
  private final AffineTransform zoomTransform = new AffineTransform();
  private final transient Image img;
  private final Rectangle imgrect;
  private transient ZoomHandler handler;
  private transient DragScrollListener listener;

  protected ZoomAndPanePanel(Image img) {
    super();
    this.img = img;
    this.imgrect = new Rectangle(img.getWidth(this), img.getHeight(this));
  }

  @Override protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(new Color(0x55_FF_00_00, true));
    Rectangle r = new Rectangle(500, 140, 150, 150);

    // use: AffineTransform#concatenate(...) and Graphics2D#setTransform(...)
    // AffineTransform at = g2.getTransform();
    // at.concatenate(zoomTransform);
    // g2.setTransform(at);
    // g2.drawImage(img, 0, 0, this);
    // g2.fill(r);

    // or use: Graphics2D#drawImage(Image, AffineTransform, ImageObserver)
    g2.drawImage(img, zoomTransform, this);
    // or: g2.drawRenderedImage((RenderedImage) img, zoomTransform);
    g2.fill(zoomTransform.createTransformedShape(r));

    // BAD EXAMPLE
    // g2.setTransform(zoomTransform);
    // g2.drawImage(img, 0, 0, this);

    g2.dispose();
  }

  @Override public Dimension getPreferredSize() {
    Rectangle r = zoomTransform.createTransformedShape(imgrect).getBounds();
    return new Dimension(r.width, r.height);
  }

  @Override public void updateUI() {
    removeMouseListener(listener);
    removeMouseMotionListener(listener);
    removeMouseWheelListener(handler);
    super.updateUI();
    listener = new DragScrollListener();
    addMouseListener(listener);
    addMouseMotionListener(listener);
    handler = new ZoomHandler();
    addMouseWheelListener(handler);
  }

  protected class ZoomHandler extends MouseAdapter {
    private static final double ZOOM_MULTIPLICATION_FACTOR = 1.2;
    private static final int MIN_ZOOM = -10;
    private static final int MAX_ZOOM = 10;
    private static final int EXTENT = 1;
    private final BoundedRangeModel zoomRange = new DefaultBoundedRangeModel(
        0, EXTENT, MIN_ZOOM, MAX_ZOOM + EXTENT);
    @Override public void mouseWheelMoved(MouseWheelEvent e) {
      int dir = e.getWheelRotation();
      int z = zoomRange.getValue();
      zoomRange.setValue(z + EXTENT * (dir > 0 ? -1 : 1));
      if (z != zoomRange.getValue()) {
        Component c = e.getComponent();
        Container p = SwingUtilities.getAncestorOfClass(JViewport.class, c);
        if (p instanceof JViewport) {
          JViewport vport = (JViewport) p;
          Rectangle ovr = vport.getViewRect();
          double s = dir > 0 ? 1d / ZOOM_MULTIPLICATION_FACTOR
                             : ZOOM_MULTIPLICATION_FACTOR;
          zoomTransform.scale(s, s);
          // double s = 1d + zoomRange.getValue() * .1;
          // zoomTransform.setToScale(s, s);
          Rectangle nvr = AffineTransform.getScaleInstance(s, s)
                                         .createTransformedShape(ovr)
                                         .getBounds();
          Point vp = nvr.getLocation();
          vp.translate(
              (nvr.width - ovr.width) / 2,
              (nvr.height - ovr.height) / 2);
          vport.setViewPosition(vp);
          c.revalidate();
          c.repaint();
        }
      }
    }
  }
}

References

2015/03/30

Create a translucent JScrollBar

Code

public JComponent makeTranslucentScrollBar(JComponent c) {
  JScrollPane scrollPane = new JScrollPane(c) {
    @Override public boolean isOptimizedDrawingEnabled() {
      return false; // JScrollBar is overlap
    }
  };
  scrollPane.setVerticalScrollBarPolicy(
      ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
  scrollPane.setHorizontalScrollBarPolicy(
      ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

  scrollPane.setComponentZOrder(scrollPane.getVerticalScrollBar(), 0);
  scrollPane.setComponentZOrder(scrollPane.getViewport(), 1);
  scrollPane.getVerticalScrollBar().setOpaque(false);

  scrollPane.setLayout(new ScrollPaneLayout() {
    @Override public void layoutContainer(Container parent) {
      JScrollPane scrollPane = (JScrollPane) parent;

      Rectangle availR = scrollPane.getBounds();
      availR.x = availR.y = 0;

      Insets insets = parent.getInsets();
      availR.x = insets.left;
      availR.y = insets.top;
      availR.width  -= insets.left + insets.right;
      availR.height -= insets.top  + insets.bottom;

      Rectangle vsbR = new Rectangle();
      vsbR.width  = 12;
      vsbR.height = availR.height;
      vsbR.x = availR.x + availR.width - vsbR.width;
      vsbR.y = availR.y;

      if (viewport != null) {
        viewport.setBounds(availR);
      }
      if (vsb != null) {
        vsb.setVisible(true);
        vsb.setBounds(vsbR);
      }
    }
  });
  scrollPane.getVerticalScrollBar().setUI(new BasicScrollBarUI() {
    private final Color defaultColor  = new Color(220, 100, 100, 100);
    private final Color draggingColor = new Color(200, 100, 100, 100);
    private final Color rolloverColor = new Color(255, 120, 100, 100);
    private final Dimension d = new Dimension();

    @Override protected JButton createDecreaseButton(int orientation) {
      return new JButton() {
        @Override public Dimension getPreferredSize() {
          return d;
        }
      };
    }

    @Override protected JButton createIncreaseButton(int orientation) {
      return new JButton() {
        @Override public Dimension getPreferredSize() {
          return d;
        }
      };
    }

    @Override protected void paintTrack(Graphics g, JComponent c, Rectangle r) {}

    @Override protected void paintThumb(Graphics g, JComponent c, Rectangle r) {
      Color color;
      JScrollBar sb = (JScrollBar) c;
      if (!sb.isEnabled() || r.width > r.height) {
        return;
      } else if (isDragging) {
        color = draggingColor;
      } else if (isThumbRollover()) {
        color = rolloverColor;
      } else {
        color = defaultColor;
      }
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setPaint(color);
      g2.fillRect(r.x, r.y, r.width - 1, r.height - 1);
      g2.setPaint(Color.WHITE);
      g2.drawRect(r.x, r.y, r.width - 1, r.height - 1);
      g2.dispose();
    }
    @Override protected void setThumbBounds(int x, int y, int width, int height) {
      super.setThumbBounds(x, y, width, height);
      // scrollbar.repaint(x, 0, width, scrollbar.getHeight());
      scrollbar.repaint();
    }
  });
  return scrollPane;
}

References

2014/09/01

Forward a mouse wheel scroll event in nested JScrollPane to the parent JScrollPane

Code

class WheelScrollLayerUI extends LayerUI<JScrollPane> {
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer) c).setLayerEventMask(AWTEvent.MOUSE_WHEEL_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseWheelEvent(
      MouseWheelEvent e, JLayer<? extends JScrollPane> l) {
    Component c = e.getComponent();
    int dir = e.getWheelRotation();
    JScrollPane main = l.getView();
    if (c instanceof JScrollPane && !c.equals(main)) {
      JScrollPane child = (JScrollPane) c;
      BoundedRangeModel m = child.getVerticalScrollBar().getModel();
      int ext = m.getExtent();
      int min = m.getMinimum();
      int max = m.getMaximum();
      int value = m.getValue();
      if (value + ext >= max && dir > 0 || value <= min && dir < 0) {
        main.dispatchEvent(SwingUtilities.convertMouseEvent(c, e, main));
      }
    }
  }
}

References

2013/05/29

Column spanning TableCellRenderer

Code

class ColumnSpanningCellRenderer extends JPanel implements TableCellRenderer {
  private static final int TARGET_COLIDX = 0;
  private final JTextArea textArea = new JTextArea(2, 999999);
  private final JLabel label = new JLabel();
  private final JLabel iconLabel = new JLabel();
  private final JScrollPane scroll = new JScrollPane(textArea);

  protected ColumnSpanningCellRenderer() {
    super(new BorderLayout());

    scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
    scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    scroll.setBorder(BorderFactory.createEmptyBorder());
    scroll.setViewportBorder(BorderFactory.createEmptyBorder());
    scroll.setOpaque(false);
    scroll.getViewport().setOpaque(false);

    textArea.setBorder(BorderFactory.createEmptyBorder());
    textArea.setMargin(new Insets(0, 0, 0, 0));
    textArea.setForeground(Color.RED);
    textArea.setEditable(false);
    textArea.setFocusable(false);
    textArea.setOpaque(false);

    iconLabel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
    iconLabel.setOpaque(false);

    Border b1 = BorderFactory.createEmptyBorder(2, 2, 2, 2);
    Border b2 = BorderFactory.createMatteBorder(0, 0, 1, 1, Color.GRAY);
    label.setBorder(BorderFactory.createCompoundBorder(b2, b1));

    setBackground(textArea.getBackground());
    setOpaque(true);
    add(label, BorderLayout.NORTH);
    add(scroll);
  }
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected,
      boolean hasFocus, int row, int column) {
    OptionPaneDescription d;
    if (value instanceof OptionPaneDescription) {
      d = (OptionPaneDescription) value;
      add(iconLabel, BorderLayout.WEST);
    } else {
      String title = Objects.toString(value, "");
      int mrow = table.convertRowIndexToModel(row);
      Object o = table.getModel().getValueAt(mrow, 0);
      if (o instanceof OptionPaneDescription) {
        OptionPaneDescription t = (OptionPaneDescription) o;
        d = new OptionPaneDescription(title, t.icon, t.text);
      } else {
        d = new OptionPaneDescription(title, null, "");
      }
      remove(iconLabel);
    }
    label.setText(d.title);
    textArea.setText(d.text);
    iconLabel.setIcon(d.icon);

    Rectangle cr = table.getCellRect(row, column, false);
    if (column != TARGET_COLIDX) {
      cr.x -= iconLabel.getPreferredSize().width;
    }
    scroll.getViewport().setViewPosition(cr.getLocation());

    if (isSelected) {
      setBackground(Color.ORANGE);
    } else {
      setBackground(Color.WHITE);
    }
    return this;
  }
}

class OptionPaneDescription {
  public final String title;
  public final Icon icon;
  public final String text;
  protected OptionPaneDescription(String title, Icon icon, String text) {
    this.title = title;
    this.icon  = icon;
    this.text  = text;
  }
}

References

2013/01/28

JScrollBar search highlighter

Code

Override WindowsScrollBarUI#paintTrack(...)

scrollbar.setUI(new WindowsScrollBarUI() {
  @Override protected void paintTrack(
      Graphics g, JComponent c, Rectangle trackBounds) {
    super.paintTrack(g, c, trackBounds);

    Rectangle rect = textArea.getBounds();
    double sy = trackBounds.getHeight() / rect.getHeight();
    AffineTransform at = AffineTransform.getScaleInstance(1.0, sy);
    Highlighter highlighter = textArea.getHighlighter();
    g.setColor(Color.YELLOW);
    try {
      for (Highlighter.Highlight hh : highlighter.getHighlights()) {
        Rectangle r = textArea.modelToView(hh.getStartOffset());
        Rectangle s = at.createTransformedShape(r).getBounds();
        int h = 2; // Math.max(2, s.height - 2);
        g.fillRect(trackBounds.x, trackBounds.y + s.y, trackBounds.width, h);
      }
    }catch (BadLocationException ex) {
      ex.printStackTrace();
    }
  }
});

JLabel + Icon + RowHeader

Code

final JScrollPane scroll = new JScrollPane(textArea);
JLabel label = new JLabel(new Icon() {
  private final Color THUMB_COLOR = new Color(0, 0, 255, 50);
  private final Rectangle thumbRect = new Rectangle();
  private final JTextComponent textArea;
  private final JScrollBar scrollbar;

  public HighlightIcon(JTextComponent textArea, JScrollBar scrollbar) {
    this.textArea = textArea;
    this.scrollbar = scrollbar;
  }

  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    // Rectangle rect   = textArea.getBounds();
    // Dimension sbSize = scrollbar.getSize();
    // Insets sbInsets  = scrollbar.getInsets();
    // double sy = (sbSize.height - sbInsets.top - sbInsets.bottom) / rect.getHeight();
    int itop = scrollbar.getInsets().top;
    BoundedRangeModel range = scrollbar.getModel();
    double sy = range.getExtent() / (double) (range.getMaximum() - range.getMinimum());
    AffineTransform at = AffineTransform.getScaleInstance(1.0, sy);
    Highlighter highlighter = textArea.getHighlighter();

    // paint Highlight
    g.setColor(Color.RED);
    try {
      for (Highlighter.Highlight hh: highlighter.getHighlights()) {
        Rectangle r = textArea.modelToView(hh.getStartOffset());
        Rectangle s = at.createTransformedShape(r).getBounds();
        int h = 2; // Math.max(2, s.height - 2);
        g.fillRect(x, y + itop + s.y, getIconWidth(), h);
      }
    } catch (BadLocationException e) {
      e.printStackTrace();
    }

    // paint Thumb
    if (scrollbar.isVisible()) {
      // JViewport vport = Objects.requireNonNull(
      //   (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, textArea));
      // Rectangle thumbRect = vport.getBounds();
      thumbRect.height = range.getExtent();
      thumbRect.y = range.getValue(); // vport.getViewPosition().y;
      g.setColor(THUMB_COLOR);
      Rectangle s = at.createTransformedShape(thumbRect).getBounds();
      g.fillRect(x, y + itop + s.y, getIconWidth(), s.height);
    }
  }

  @Override public int getIconWidth() {
    return 8;
  }

  @Override public int getIconHeight() {
    JViewport vport = Objects.requireNonNull(
      (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, textArea));
    return vport.getHeight();
  }
});

scroll.setVerticalScrollBar(scrollbar);
/*
// Fixed Versions: 7 (b134)
scroll.setRowHeaderView(label);
/*/
// 6826074 JScrollPane does not revalidate the component hierarchy after scrolling
// https://bugs.openjdk.org/browse/JDK-6826074
// Affected Versions: 6u12,6u16,7
JViewport vp = new JViewport() {
  @Override public void setViewPosition(Point p) {
    super.setViewPosition(p);
    revalidate();
  }
};
vp.setView(label);
scroll.setRowHeader(vp);

MatteBorder + Icon + RowHeader

Code

JScrollBar scrollBar = new JScrollBar(Adjustable.VERTICAL) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.width += getInsets().left;
    return d;
  }

  @Override public void updateUI() {
    super.updateUI();
    setBorder(BorderFactory.createMatteBorder(0, 4, 0, 0, new Icon() {
      @Override public void paintIcon(Component c, Graphics g, int x, int y) {
        // ...
      }

      @Override public int getIconWidth() {
        return getInsets().left;
      }

      @Override public int getIconHeight() {
        return getHeight();
      }
    }));
  }
};
scroll.setVerticalScrollBar(scrollBar);

References

2012/06/21

Height of JTableHeader

Code

JScrollPane scroll = new JScrollPane(table);
scroll.setColumnHeader(new JViewport() {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 32;
    return d;
  }
});
//or
table.setTableHeader(new JTableHeader(table.getColumnModel()) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 32;
    return d;
  }
});

References