test/jdk/sanity/client/lib/SwingSet3/src/com/sun/swingset3/demos/table/HyperlinkCellRenderer.java
changeset 49298 9f19db69967a
equal deleted inserted replaced
49297:ac821c698c3a 49298:9f19db69967a
       
     1 /*
       
     2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 package com.sun.swingset3.demos.table;
       
    25 
       
    26 import java.awt.Color;
       
    27 import java.awt.Component;
       
    28 import java.awt.Cursor;
       
    29 import java.awt.Insets;
       
    30 import java.awt.Point;
       
    31 import java.awt.Rectangle;
       
    32 import java.awt.event.ActionEvent;
       
    33 import java.awt.event.ActionListener;
       
    34 import java.awt.event.MouseAdapter;
       
    35 import java.awt.event.MouseEvent;
       
    36 import java.util.ArrayList;
       
    37 import java.util.HashMap;
       
    38 
       
    39 import javax.swing.Action;
       
    40 import javax.swing.JTable;
       
    41 import javax.swing.SwingUtilities;
       
    42 import javax.swing.UIManager;
       
    43 import javax.swing.border.Border;
       
    44 import javax.swing.border.EmptyBorder;
       
    45 import javax.swing.table.TableCellRenderer;
       
    46 
       
    47 import com.sun.swingset3.demos.JHyperlink;
       
    48 
       
    49 /**
       
    50  * Table renderer which renders cell value as hyperlink with optional rollover underline.
       
    51  *
       
    52  * @author aim
       
    53  */
       
    54 public class HyperlinkCellRenderer extends JHyperlink implements TableCellRenderer {
       
    55     private JTable table;
       
    56     private final ArrayList<Integer> columnModelIndeces = new ArrayList<Integer>();
       
    57 
       
    58     private Color rowColors[];
       
    59     private Color foreground;
       
    60     private Color visitedForeground;
       
    61     private Border focusBorder;
       
    62     private Border noFocusBorder;
       
    63 
       
    64     private boolean underlineOnRollover = true;
       
    65 
       
    66     private transient int hitColumnIndex = -1;
       
    67     private transient int hitRowIndex = -1;
       
    68 
       
    69     private HashMap<Object, int[]> visitedCache;
       
    70 
       
    71     public HyperlinkCellRenderer(Action action, boolean underlineOnRollover) {
       
    72         setAction(action);
       
    73         setHorizontalAlignment(JHyperlink.LEFT);
       
    74         rowColors = new Color[1];
       
    75         rowColors[0] = UIManager.getColor("Table.background");
       
    76         this.underlineOnRollover = underlineOnRollover;
       
    77         applyDefaults();
       
    78     }
       
    79 
       
    80     public void setRowColors(Color[] colors) {
       
    81         this.rowColors = colors;
       
    82     }
       
    83 
       
    84     public void updateUI() {
       
    85         super.updateUI();
       
    86         applyDefaults();
       
    87     }
       
    88 
       
    89     protected void applyDefaults() {
       
    90         setOpaque(true);
       
    91         setBorderPainted(false);
       
    92         foreground = UIManager.getColor("Hyperlink.foreground");
       
    93         visitedForeground = UIManager.getColor("Hyperlink.visitedForeground");
       
    94 
       
    95         // Make sure border used on non-focussed cells is same size as focussed border
       
    96         focusBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
       
    97         if (focusBorder != null) {
       
    98             Insets insets = focusBorder.getBorderInsets(this);
       
    99             noFocusBorder = new EmptyBorder(insets.top, insets.left, insets.bottom, insets.right);
       
   100         } else {
       
   101             focusBorder = noFocusBorder = new EmptyBorder(1, 1, 1, 1);
       
   102         }
       
   103     }
       
   104 
       
   105     public Component getTableCellRendererComponent(JTable table, Object value,
       
   106             boolean isSelected, boolean hasFocus, int row, int column) {
       
   107         if (this.table == null) {
       
   108             this.table = table;
       
   109             HyperlinkMouseListener hyperlinkListener = new HyperlinkMouseListener();
       
   110             table.addMouseMotionListener(hyperlinkListener);
       
   111             table.addMouseListener(hyperlinkListener);
       
   112         }
       
   113         int columnModelIndex = table.getColumnModel().getColumn(column).getModelIndex();
       
   114         if (!columnModelIndeces.contains(columnModelIndex)) {
       
   115             columnModelIndeces.add(columnModelIndex);
       
   116         }
       
   117 
       
   118         if (value instanceof Link) {
       
   119             Link link = (Link) value;
       
   120             setText(link.getDisplayText());
       
   121             setToolTipText(link.getDescription());
       
   122         } else {
       
   123             setText(value != null ? value.toString() : "");
       
   124         }
       
   125         setVisited(isCellLinkVisited(value, row, column));
       
   126         setDrawUnderline(!underlineOnRollover ||
       
   127                 (row == hitRowIndex && column == hitColumnIndex));
       
   128 
       
   129         if (!isSelected) {
       
   130             setBackground(rowColors[row % rowColors.length]);
       
   131             //setForeground(isCellLinkVisited(value, row, column)?
       
   132             //  visitedForeground : foreground);
       
   133             setForeground(foreground);
       
   134             setVisitedForeground(visitedForeground);
       
   135         } else {
       
   136             setBackground(table.getSelectionBackground());
       
   137             setForeground(table.getSelectionForeground());
       
   138             setVisitedForeground(table.getSelectionForeground());
       
   139         }
       
   140         //setBorder(hasFocus? focusBorder : noFocusBorder);
       
   141         //System.out.println("border insets="+getBorder().getBorderInsets(this));
       
   142 
       
   143         return this;
       
   144     }
       
   145 
       
   146     protected void setCellLinkVisited(Object value, int row, int column) {
       
   147         if (!isCellLinkVisited(value, row, column)) {
       
   148             if (value instanceof Link) {
       
   149                 ((Link) value).setVisited(true);
       
   150             } else {
       
   151                 if (visitedCache == null) {
       
   152                     visitedCache = new HashMap<Object, int[]>();
       
   153                 }
       
   154                 int position[] = new int[2];
       
   155                 position[0] = table.convertRowIndexToModel(row);
       
   156                 position[1] = table.convertColumnIndexToModel(column);
       
   157                 visitedCache.put(value, position);
       
   158             }
       
   159         }
       
   160     }
       
   161 
       
   162     protected boolean isCellLinkVisited(Object value, int row, int column) {
       
   163         if (value instanceof Link) {
       
   164             return ((Link) value).isVisited();
       
   165         }
       
   166         if (visitedCache != null) {
       
   167             int position[] = visitedCache.get(value);
       
   168             if (position != null) {
       
   169                 return position[0] == table.convertRowIndexToModel(row) &&
       
   170                         position[1] == table.convertColumnIndexToModel(column);
       
   171             }
       
   172         }
       
   173         return false;
       
   174     }
       
   175 
       
   176     public int getActiveHyperlinkRow() {
       
   177         return hitRowIndex;
       
   178     }
       
   179 
       
   180     public int getActiveHyperlinkColumn() {
       
   181         return hitColumnIndex;
       
   182     }
       
   183 
       
   184     // overridden because the AbstractButton's version forces the source of the event
       
   185     // to be the AbstractButton and we want a little more freedom to configure the
       
   186     // event
       
   187     @Override
       
   188     protected void fireActionPerformed(ActionEvent event) {
       
   189         // Guaranteed to return a non-null array
       
   190         Object[] listeners = listenerList.getListenerList();
       
   191 
       
   192         // Process the listeners last to first, notifying
       
   193         // those that are interested in this event
       
   194         for (int i = listeners.length - 2; i >= 0; i -= 2) {
       
   195             if (listeners[i] == ActionListener.class) {
       
   196                 ((ActionListener) listeners[i + 1]).actionPerformed(event);
       
   197             }
       
   198         }
       
   199     }
       
   200 
       
   201     public void invalidate() {
       
   202     }
       
   203 
       
   204     public void validate() {
       
   205     }
       
   206 
       
   207     public void revalidate() {
       
   208     }
       
   209 
       
   210     public void repaint(long tm, int x, int y, int width, int height) {
       
   211     }
       
   212 
       
   213     public void repaint(Rectangle r) {
       
   214     }
       
   215 
       
   216     public void repaint() {
       
   217     }
       
   218 
       
   219     private class HyperlinkMouseListener extends MouseAdapter {
       
   220         private transient Rectangle cellRect;
       
   221         private final transient Rectangle iconRect = new Rectangle();
       
   222         private final transient Rectangle textRect = new Rectangle();
       
   223         private transient Cursor tableCursor;
       
   224 
       
   225         @Override
       
   226         public void mouseMoved(MouseEvent event) {
       
   227             // This should only be called if underlineOnRollover is true
       
   228             JTable table = (JTable) event.getSource();
       
   229 
       
   230             // Locate the table cell under the event location
       
   231             int oldHitColumnIndex = hitColumnIndex;
       
   232             int oldHitRowIndex = hitRowIndex;
       
   233 
       
   234             checkIfPointInsideHyperlink(event.getPoint());
       
   235 
       
   236             if (hitRowIndex != oldHitRowIndex ||
       
   237                     hitColumnIndex != oldHitColumnIndex) {
       
   238                 if (hitRowIndex != -1) {
       
   239                     if (tableCursor == null) {
       
   240                         tableCursor = table.getCursor();
       
   241                     }
       
   242                     table.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
       
   243                 } else {
       
   244                     table.setCursor(tableCursor);
       
   245                 }
       
   246 
       
   247                 // repaint the cells affected by rollover
       
   248                 Rectangle repaintRect;
       
   249                 if (hitRowIndex != -1 && hitColumnIndex != -1) {
       
   250                     // we need to repaint new cell with rollover underline
       
   251                     // cellRect already contains rect of hit cell
       
   252                     if (oldHitRowIndex != -1 && oldHitColumnIndex != -1) {
       
   253                         // we also need to repaint previously underlined hyperlink cell
       
   254                         // to remove the underline
       
   255                         repaintRect = cellRect.union(
       
   256                                 table.getCellRect(oldHitRowIndex, oldHitColumnIndex, false));
       
   257                     } else {
       
   258                         // we don't have a previously underlined hyperlink, so just repaint new one'
       
   259                         repaintRect = table.getCellRect(hitRowIndex, hitColumnIndex, false);
       
   260                     }
       
   261                 } else {
       
   262                     // we just need to repaint previously underlined hyperlink cell
       
   263                     //to remove the underline
       
   264                     repaintRect = table.getCellRect(oldHitRowIndex, oldHitColumnIndex, false);
       
   265                 }
       
   266                 table.repaint(repaintRect);
       
   267             }
       
   268 
       
   269         }
       
   270 
       
   271         @Override
       
   272         public void mouseClicked(MouseEvent event) {
       
   273             if (checkIfPointInsideHyperlink(event.getPoint())) {
       
   274 
       
   275                 ActionEvent actionEvent = new ActionEvent(new Integer(hitRowIndex),
       
   276                         ActionEvent.ACTION_PERFORMED,
       
   277                         "hyperlink");
       
   278 
       
   279                 HyperlinkCellRenderer.this.fireActionPerformed(actionEvent);
       
   280 
       
   281                 setCellLinkVisited(table.getValueAt(hitRowIndex, hitColumnIndex),
       
   282                         hitRowIndex, hitColumnIndex);
       
   283 
       
   284             }
       
   285         }
       
   286 
       
   287         protected boolean checkIfPointInsideHyperlink(Point p) {
       
   288             hitColumnIndex = table.columnAtPoint(p);
       
   289             hitRowIndex = table.rowAtPoint(p);
       
   290 
       
   291             if (hitColumnIndex != -1 && hitRowIndex != -1 &&
       
   292                     columnModelIndeces.contains(table.getColumnModel().
       
   293                             getColumn(hitColumnIndex).getModelIndex())) {
       
   294                 // We know point is within a hyperlink column, however we do further hit testing
       
   295                 // to see if point is within the text bounds on the hyperlink
       
   296                 TableCellRenderer renderer = table.getCellRenderer(hitRowIndex, hitColumnIndex);
       
   297                 JHyperlink hyperlink = (JHyperlink) table.prepareRenderer(renderer, hitRowIndex, hitColumnIndex);
       
   298 
       
   299                 // Convert the event to the renderer's coordinate system
       
   300                 cellRect = table.getCellRect(hitRowIndex, hitColumnIndex, false);
       
   301                 hyperlink.setSize(cellRect.width, cellRect.height);
       
   302                 p.translate(-cellRect.x, -cellRect.y);
       
   303                 cellRect.x = cellRect.y = 0;
       
   304                 iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
       
   305                 textRect.x = textRect.y = textRect.width = textRect.height = 0;
       
   306                 SwingUtilities.layoutCompoundLabel(
       
   307                         hyperlink.getFontMetrics(hyperlink.getFont()),
       
   308                         hyperlink.getText(), hyperlink.getIcon(),
       
   309                         hyperlink.getVerticalAlignment(),
       
   310                         hyperlink.getHorizontalAlignment(),
       
   311                         hyperlink.getVerticalTextPosition(),
       
   312                         hyperlink.getHorizontalTextPosition(),
       
   313                         cellRect, iconRect, textRect, hyperlink.getIconTextGap());
       
   314 
       
   315                 if (textRect.contains(p)) {
       
   316                     // point is within hyperlink text bounds
       
   317                     return true;
       
   318                 }
       
   319             }
       
   320             // point is not within a hyperlink's text bounds
       
   321             hitRowIndex = -1;
       
   322             hitColumnIndex = -1;
       
   323             return false;
       
   324         }
       
   325     }
       
   326 }