JXTable table = new JXTable(model); table.setEditable(false); AbstractHyperlinkAction<URI> act = new AbstractHyperlinkAction<URI>() { public void actionPerformed(ActionEvent ev) { try { Desktop.getDesktop().browse(target); } catch (Exception e) { e.printStackTrace(); } } }; table.setDefaultRenderer(URI.class, new DefaultTableRenderer(new HyperlinkProvider(act)));This is nice but if you hover with your mouse over a cell which contains a hyperlink it will be rendered in rollover state even if the mouse is not really above the link text. That's because the rollover support in SwingX works on a cell basis. This also has the unfortunate effect of triggering the associated link action as soon as you click inside the cell even if you didn't click on the link itself. This is counterintuitive because that's not how links usually work. Fortunately we can modify this behavior by overriding the relevant parts in HyperlinkProvider and JXTable.
First of all we need a way to determine if the mouse is hovering above the actual text of a cell or not. For this purpose we implement the utility function isRolloverText:
static private boolean isRolloverText(JXTable tbl, int row, int col) { int mCol = tbl.convertColumnIndexToModel(col); int mRow = tbl.convertRowIndexToModel(row); if (tbl.getColumnClass(col) != URI.class) { return false; } Point pos = tbl.getMousePosition(); if (pos == null) { return false; } AbstractRenderer ren = (AbstractRenderer)tbl.getCellRenderer(row, col); Object value = tbl.getModel().getValueAt(mRow, mCol); ComponentProvider<? extends JComponent> prov = ren .getComponentProvider(); String text = prov.getString(value); if (text.length() == 0) { return false; } JComponent com = prov.getRendererComponent(null); int textWidth = com.getFontMetrics(com.getFont()) .stringWidth(text); Rectangle cellBounds = tbl.getCellRect(row, col, true); Rectangle textBounds = new Rectangle( cellBounds.x, cellBounds.y, textWidth, cellBounds.height); return textBounds.contains(pos); }Note that this implementation assumes that the whole cell is used for rendering the text. For example, if you customize the hyperlink cell renderer to render an icon on the left side of the text this code wouldn't work properly anymore.
Now that we have a way to tell if the mouse is above the cell text we can override the HyperlinkProvider to only set the rollover state if the cursor is really above the text. In this example I just copied the implementation from the superclass and added the highlighted line:
@Override protected void configureState(CellContext context) { if (context.getComponent() == null) { return; } Point p = (Point) context.getComponent() .getClientProperty(RolloverProducer.ROLLOVER_KEY); if (p != null && (p.x >= 0) && (p.x == context.getColumn()) && (p.y == context.getRow()) && isRolloverText((JXTable)context.getComponent(), p.y, p.x) && !rendererComponent.getModel().isRollover()) { rendererComponent.getModel().setRollover(true); } else if (rendererComponent.getModel().isRollover()) { rendererComponent.getModel().setRollover(false); } }Then we also need to modify the JXTable's rollover support:
@Override protected RolloverProducer createRolloverProducer() { return new TableRolloverProducer() { @Override public void mouseMoved(MouseEvent ev) { updateRollover(ev, ROLLOVER_KEY, true); } }; } @Override protected TableRolloverController<JXTable> createLinkController() { return new TableRolloverController<JXTable>() { @Override protected RolloverRenderer getRolloverRenderer( Point loc, boolean prep) { if (getColumnClass(loc.x) == URI.class && !isRolloverText(component, loc.y, loc.x)) { return null; } return super.getRolloverRenderer(loc, prep); } }; }And that's it:
package sendtehcodez; import java.awt.Desktop; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.net.URI; import java.net.URISyntaxException; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.UIManager; import javax.swing.table.DefaultTableModel; import org.jdesktop.swingx.JXTable; import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction; import org.jdesktop.swingx.renderer.AbstractRenderer; import org.jdesktop.swingx.renderer.CellContext; import org.jdesktop.swingx.renderer.ComponentProvider; import org.jdesktop.swingx.renderer.DefaultTableRenderer; import org.jdesktop.swingx.renderer.HyperlinkProvider; import org.jdesktop.swingx.rollover.RolloverProducer; import org.jdesktop.swingx.rollover.RolloverRenderer; import org.jdesktop.swingx.rollover.TableRolloverController; import org.jdesktop.swingx.rollover.TableRolloverProducer; public class TablesWithHyperlinks { public static void main(String[] args) throws Exception { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); JScrollPane scroll = new JScrollPane(createTable()); scroll.setBorder(BorderFactory.createEmptyBorder()); final JFrame f = new JFrame("Tables with Hyperlinks"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(scroll); f.setSize(400, 300); f.setLocationRelativeTo(null); f.setVisible(true); } static private boolean isRolloverText(JXTable tbl, int row, int col) { int mCol = tbl.convertColumnIndexToModel(col); int mRow = tbl.convertRowIndexToModel(row); if (tbl.getColumnClass(col) != URI.class) { return false; } Point pos = tbl.getMousePosition(); if (pos == null) { return false; } AbstractRenderer ren = (AbstractRenderer)tbl.getCellRenderer(row, col); Object value = tbl.getModel().getValueAt(mRow, mCol); ComponentProvider<? extends JComponent> prov = ren .getComponentProvider(); String text = prov.getString(value); if (text.length() == 0) { return false; } JComponent com = prov.getRendererComponent(null); int textWidth = com.getFontMetrics(com.getFont()) .stringWidth(text); Rectangle cellBounds = tbl.getCellRect(row, col, true); Rectangle textBounds = new Rectangle( cellBounds.x, cellBounds.y, textWidth, cellBounds.height); return textBounds.contains(pos); } static JXTable createTable() throws URISyntaxException { Object[][] data = new Object[][] { { new URI("http://java.oracle.com"), "Java" }, { new URI("http://clojure.org"), "Clojure" }, { new URI("http://haskell.org"), "Haskell" }, { new URI("http://erlang.org"), "Erlang" }, { new URI("http://python.org"), "Python" }, { new URI("http://ruby-lang.org"), "Ruby" } }; DefaultTableModel model = new DefaultTableModel(data, new Object[] { "URI", "Language" }) { @Override public Class<?> getColumnClass(int col) { if (col == 0) { return URI.class; } return super.getColumnClass(col); } }; JXTable table = new JXTable(model) { @Override protected RolloverProducer createRolloverProducer() { return new TableRolloverProducer() { @Override public void mouseMoved(MouseEvent ev) { updateRollover(ev, ROLLOVER_KEY, true); } }; } @Override protected TableRolloverController<JXTable> createLinkController() { return new TableRolloverController<JXTable>() { @Override protected RolloverRenderer getRolloverRenderer( Point loc, boolean prep) { if (getColumnClass(loc.x) == URI.class && !isRolloverText(component, loc.y, loc.x)) { return null; } return super.getRolloverRenderer(loc, prep); } }; } }; AbstractHyperlinkAction<URI> act = new AbstractHyperlinkAction<URI>() { public void actionPerformed(ActionEvent ev) { try { Desktop.getDesktop().browse(target); } catch (Exception e) { e.printStackTrace(); } } }; HyperlinkProvider hp = new HyperlinkProvider(act) { @Override protected void configureState(CellContext context) { if (context.getComponent() == null) { return; } Point p = (Point) context.getComponent() .getClientProperty(RolloverProducer.ROLLOVER_KEY); if (p != null && (p.x >= 0) && (p.x == context.getColumn()) && (p.y == context.getRow()) && isRolloverText((JXTable)context.getComponent(), p.y, p.x) && !rendererComponent.getModel().isRollover()) { rendererComponent.getModel().setRollover(true); } else if (rendererComponent.getModel().isRollover()) { rendererComponent.getModel().setRollover(false); } } }; table.setDefaultRenderer(URI.class, new DefaultTableRenderer(hp)); table.setEditable(false); table.setColumnControlVisible(true); return table; } }