|
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 } |