|
1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.util.swing; |
|
27 |
|
28 import java.awt.*; |
|
29 import java.awt.event.*; |
|
30 import java.awt.geom.Area; |
|
31 import java.util.Vector; |
|
32 import javax.swing.*; |
|
33 import javax.swing.border.Border; |
|
34 import javax.swing.event.*; |
|
35 import javax.swing.table.*; |
|
36 import com.oracle.solaris.vp.util.swing.glass.BlockingGlassPane; |
|
37 |
|
38 @SuppressWarnings({"serial"}) |
|
39 public class OverlayTable extends ExtTable { |
|
40 // |
|
41 // Instance data |
|
42 // |
|
43 |
|
44 private ShadowPanel shadowPanel; |
|
45 private JPanel contentPane; |
|
46 private JPanel rendererPane; |
|
47 private OverlayComponentFactory factory; |
|
48 private int vRow = -1; |
|
49 private boolean overlaySpansFullWidth = true; |
|
50 |
|
51 private ListSelectionListener selectionListener = |
|
52 new ListSelectionListener() { |
|
53 @Override |
|
54 public void valueChanged(ListSelectionEvent e) { |
|
55 if (e.getValueIsAdjusting()) { |
|
56 hideOverlay(); |
|
57 } else { |
|
58 int[] rows = getSelectedRows(); |
|
59 if (rows.length == 1) { |
|
60 if (isEnabled()) { |
|
61 showOverlay(rows[0]); |
|
62 } |
|
63 } |
|
64 } |
|
65 } |
|
66 }; |
|
67 |
|
68 private MouseListener clickListener = |
|
69 new MouseAdapter() { |
|
70 @Override |
|
71 public void mouseClicked(MouseEvent e) { |
|
72 if (GUIUtil.isUnmodifiedClick(e, 2)) { |
|
73 int vRow = rowAtPoint(e.getPoint()); |
|
74 if (vRow >= 0) { |
|
75 if (isEnabled()) { |
|
76 showOverlay(vRow); |
|
77 } |
|
78 } |
|
79 } |
|
80 } |
|
81 }; |
|
82 |
|
83 // |
|
84 // Constructors |
|
85 // |
|
86 |
|
87 public OverlayTable() { |
|
88 init(); |
|
89 } |
|
90 |
|
91 public OverlayTable(int numRows, int numColumns) { |
|
92 super(numRows, numColumns); |
|
93 init(); |
|
94 } |
|
95 |
|
96 public OverlayTable(Object[][] rowData, Object[] columnNames) { |
|
97 super(rowData, columnNames); |
|
98 init(); |
|
99 } |
|
100 |
|
101 public OverlayTable(TableModel model) { |
|
102 super(model); |
|
103 init(); |
|
104 } |
|
105 |
|
106 public OverlayTable(TableModel model, TableColumnModel cModel) { |
|
107 super(model, cModel); |
|
108 init(); |
|
109 } |
|
110 |
|
111 public OverlayTable(TableModel model, TableColumnModel cModel, |
|
112 ListSelectionModel sModel) { |
|
113 |
|
114 super(model, cModel, sModel); |
|
115 init(); |
|
116 } |
|
117 |
|
118 public OverlayTable(Vector rowData, Vector columnNames) { |
|
119 super(rowData, columnNames); |
|
120 init(); |
|
121 } |
|
122 |
|
123 // |
|
124 // Container methods |
|
125 // |
|
126 |
|
127 @Override |
|
128 public void doLayout() { |
|
129 super.doLayout(); |
|
130 |
|
131 if (vRow != -1) { |
|
132 Dimension tSize = getSize(); |
|
133 shadowPanel.setBounds(0, 0, tSize.width, tSize.height); |
|
134 |
|
135 // Build renderer pane bounds |
|
136 Rectangle rBounds = getCellRect(vRow, 0, true); |
|
137 int rowHeight = rBounds.height; |
|
138 |
|
139 rBounds.height = rendererPane.getPreferredSize().height + rowHeight; |
|
140 |
|
141 Dimension cPrefSize = contentPane.getPreferredSize(); |
|
142 |
|
143 // Renderer pane overlaps the content pane's left border |
|
144 Insets cInsets = contentPane.getInsets(); |
|
145 rBounds.width += cInsets.left; |
|
146 |
|
147 // Build content pane bounds |
|
148 Rectangle cBounds = getCellRect(vRow, 1, true); |
|
149 |
|
150 // Adjust bounds to align with vertical grid line |
|
151 rBounds.width--; |
|
152 cBounds.x--; |
|
153 cBounds.width++; |
|
154 |
|
155 // Span the remaining width of the table |
|
156 cBounds.width = tSize.width - cBounds.x; |
|
157 if (!overlaySpansFullWidth) { |
|
158 cBounds.width = Math.min(cBounds.width, cPrefSize.width); |
|
159 } |
|
160 |
|
161 // Use preferred height, adjust downward if necessary |
|
162 cBounds.height = Math.min(cPrefSize.height, tSize.height); |
|
163 |
|
164 // At least as high as the renderer pane |
|
165 cBounds.height = Math.max(cBounds.height, rBounds.height); |
|
166 |
|
167 // Do bounds span beyond the bottom of the table? |
|
168 if (rBounds.y + rBounds.height > tSize.height || |
|
169 cBounds.y + cBounds.height > tSize.height) { |
|
170 |
|
171 // Align bottom edges instead |
|
172 rBounds.y += rowHeight - rBounds.height; |
|
173 cBounds.y += rowHeight - cBounds.height; |
|
174 |
|
175 // Do bounds span beyond the top of the table? |
|
176 if (cBounds.y < 0) { |
|
177 cBounds.y = 0; |
|
178 } |
|
179 |
|
180 if (rBounds.y < 0) { |
|
181 rBounds.y = 0; |
|
182 } |
|
183 } |
|
184 |
|
185 rendererPane.setBounds(rBounds); |
|
186 contentPane.setBounds(cBounds); |
|
187 rendererPane.repaint(); |
|
188 contentPane.repaint(); |
|
189 |
|
190 repaint(); |
|
191 } |
|
192 } |
|
193 |
|
194 // |
|
195 // JComponent methods |
|
196 // |
|
197 |
|
198 @Override |
|
199 public boolean isOptimizedDrawingEnabled() { |
|
200 return false; |
|
201 } |
|
202 |
|
203 // |
|
204 // OverlayTable methods |
|
205 // |
|
206 |
|
207 public void configureOverlayOnRowSelection() { |
|
208 setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
|
209 |
|
210 ListSelectionModel sModel = getSelectionModel(); |
|
211 |
|
212 // Ensure listener only added once |
|
213 sModel.removeListSelectionListener(selectionListener); |
|
214 sModel.addListSelectionListener(selectionListener); |
|
215 } |
|
216 |
|
217 public void configureOverlayOnDoubleClick() { |
|
218 // Ensure listener only added once |
|
219 removeMouseListener(clickListener); |
|
220 addMouseListener(clickListener); |
|
221 } |
|
222 |
|
223 protected JPanel getContentPane() { |
|
224 return contentPane; |
|
225 } |
|
226 |
|
227 public OverlayComponentFactory getFactory() { |
|
228 return factory; |
|
229 } |
|
230 |
|
231 public Color getOverlayBackground() { |
|
232 return contentPane.getBackground(); |
|
233 } |
|
234 |
|
235 public Border getOverlayBorder() { |
|
236 return contentPane.getBorder(); |
|
237 } |
|
238 |
|
239 public Component getOverlayComponent(int vRow) { |
|
240 if (factory != null) { |
|
241 return factory.getOverlayComponent(this, vRow); |
|
242 } |
|
243 return null; |
|
244 } |
|
245 |
|
246 public boolean getOverlaySpansFullWidth() { |
|
247 return overlaySpansFullWidth; |
|
248 } |
|
249 |
|
250 protected JPanel getRenderPane() { |
|
251 return rendererPane; |
|
252 } |
|
253 |
|
254 public void hideOverlay() { |
|
255 if (isOverlayVisible()) { |
|
256 vRow = -1; |
|
257 contentPane.setVisible(false); |
|
258 rendererPane.setVisible(false); |
|
259 revalidate(); |
|
260 repaint(); |
|
261 } |
|
262 } |
|
263 |
|
264 public boolean isOverlayShadowEnabled() { |
|
265 return shadowPanel.isShadowEnabled(); |
|
266 } |
|
267 |
|
268 public boolean isOverlayVisible() { |
|
269 return vRow != -1; |
|
270 } |
|
271 |
|
272 public Component prepareOverlayRenderer( |
|
273 TableCellRenderer renderer, int row, int column) { |
|
274 Object value = getValueAt(row, column); |
|
275 |
|
276 boolean isSelected = false; |
|
277 boolean hasFocus = false; |
|
278 |
|
279 Component comp = renderer.getTableCellRendererComponent( |
|
280 this, value, isSelected, hasFocus, row, column); |
|
281 |
|
282 if (comp instanceof JComponent) { |
|
283 ((JComponent)comp).setOpaque(false); |
|
284 } |
|
285 |
|
286 return comp; |
|
287 } |
|
288 |
|
289 public void setFactory(OverlayComponentFactory factory) { |
|
290 this.factory = factory; |
|
291 } |
|
292 |
|
293 public void setOverlayBackground(Color color) { |
|
294 contentPane.setBackground(color); |
|
295 rendererPane.setBackground(color); |
|
296 } |
|
297 |
|
298 public void setOverlayBorder(final Border border) { |
|
299 Border rendererBorder = border == null ? |
|
300 null : new ClippedBorder(border, true, true, true, false); |
|
301 |
|
302 rendererPane.setBorder(rendererBorder); |
|
303 |
|
304 Border contentBorder = null; |
|
305 if (border != null) { |
|
306 contentBorder = new Border() { |
|
307 @Override |
|
308 public Insets getBorderInsets(Component c) { |
|
309 return border.getBorderInsets(c); |
|
310 } |
|
311 |
|
312 @Override |
|
313 public boolean isBorderOpaque() { |
|
314 return border.isBorderOpaque(); |
|
315 } |
|
316 |
|
317 @Override |
|
318 public void paintBorder(Component c, Graphics g, int x, int y, |
|
319 int width, int height) { |
|
320 |
|
321 Shape clip = g.getClip(); |
|
322 |
|
323 Area newClip = new Area(clip); |
|
324 |
|
325 Rectangle cBounds = c.getBounds(); |
|
326 Rectangle rBounds = rendererPane.getBounds(); |
|
327 |
|
328 // Account for insets |
|
329 Insets rInsets = rendererPane.getInsets(); |
|
330 rBounds.translate(rInsets.left, rInsets.top); |
|
331 rBounds.width -= rInsets.left + rInsets.right; |
|
332 rBounds.height -= rInsets.top + rInsets.bottom; |
|
333 |
|
334 // Translate to clip coordinates |
|
335 rBounds.translate(-cBounds.x, -cBounds.y); |
|
336 |
|
337 newClip.subtract(new Area(rBounds)); |
|
338 g.setClip(newClip); |
|
339 |
|
340 border.paintBorder(c, g, x, y, width, height); |
|
341 |
|
342 g.setClip(clip); |
|
343 } |
|
344 }; |
|
345 } |
|
346 |
|
347 contentPane.setBorder(contentBorder); |
|
348 } |
|
349 |
|
350 public void setOverlayShadowEnabled(boolean enabled) { |
|
351 shadowPanel.setShadowEnabled(enabled); |
|
352 } |
|
353 |
|
354 public void setOverlaySpansFullWidth(boolean overlaySpansFullWidth) { |
|
355 this.overlaySpansFullWidth = overlaySpansFullWidth; |
|
356 } |
|
357 |
|
358 public void showOverlay(int vRow) { |
|
359 if (vRow != this.vRow) { |
|
360 // Hide any existing overlay |
|
361 hideOverlay(); |
|
362 |
|
363 if (vRow != -1) { |
|
364 Component oComp = getOverlayComponent(vRow); |
|
365 if (oComp != null) { |
|
366 if (oComp.getParent() != contentPane) { |
|
367 contentPane.removeAll(); |
|
368 contentPane.add(oComp, BorderLayout.CENTER); |
|
369 } |
|
370 |
|
371 this.vRow = vRow; |
|
372 contentPane.setVisible(true); |
|
373 rendererPane.setVisible(true); |
|
374 |
|
375 revalidate(); |
|
376 repaint(); |
|
377 |
|
378 EventQueue.invokeLater( |
|
379 new Runnable() { |
|
380 @Override |
|
381 public void run() { |
|
382 scrollRectToVisible(contentPane.getBounds()); |
|
383 } |
|
384 }); |
|
385 } |
|
386 } |
|
387 } |
|
388 } |
|
389 |
|
390 // |
|
391 // Private methods |
|
392 // |
|
393 |
|
394 private Component getOverlayCellRenderer() { |
|
395 TableCellRenderer renderer = getCellRenderer(vRow, 0); |
|
396 return prepareOverlayRenderer(renderer, vRow, 0); |
|
397 } |
|
398 |
|
399 private void init() { |
|
400 contentPane = new JPanel(); |
|
401 contentPane.setLayout(new BorderLayout()); |
|
402 contentPane.setOpaque(true); |
|
403 |
|
404 // Prevent clicks from passing through to table |
|
405 BlockingGlassPane.addConsumeEventListeners(contentPane); |
|
406 |
|
407 final CellRendererPane cellRendererPane = new CellRendererPane(); |
|
408 rendererPane = new JPanel() { |
|
409 @Override |
|
410 public void paintComponent(Graphics g) { |
|
411 super.paintComponent(g); |
|
412 |
|
413 Dimension spacing = getIntercellSpacing(); |
|
414 Insets insets = getInsets(); |
|
415 insets.top += spacing.height / 2; |
|
416 insets.bottom -= 1 - spacing.height / 2; |
|
417 insets.left += spacing.width / 2; |
|
418 insets.right -= 1 - spacing.width / 2; |
|
419 |
|
420 Dimension cSize = getSize(); |
|
421 |
|
422 // The amount of space we have to work with |
|
423 int cWidth = cSize.width - insets.left - insets.right; |
|
424 int cHeight = cSize.height - insets.top - insets.bottom; |
|
425 |
|
426 Component renderer = getOverlayCellRenderer(); |
|
427 |
|
428 cellRendererPane.paintComponent(g, renderer, this, insets.left, |
|
429 insets.top, cWidth, cHeight); |
|
430 } |
|
431 }; |
|
432 |
|
433 rendererPane.setOpaque(true); |
|
434 rendererPane.setLayout(new BorderLayout()); |
|
435 rendererPane.add(cellRendererPane, BorderLayout.CENTER); |
|
436 |
|
437 // Prevent clicks from passing through to table |
|
438 BlockingGlassPane.addConsumeEventListeners(rendererPane); |
|
439 |
|
440 shadowPanel = new ShadowPanel(); |
|
441 shadowPanel.setLayout(null); |
|
442 shadowPanel.setOpaque(false); |
|
443 |
|
444 shadowPanel.add(contentPane); |
|
445 shadowPanel.add(rendererPane); |
|
446 add(shadowPanel); |
|
447 |
|
448 Border border = BorderFactory.createLineBorder(Color.BLACK, 1); |
|
449 setOverlayBorder(border); |
|
450 |
|
451 getColumnModel().addColumnModelListener( |
|
452 new TableColumnModelListener() { |
|
453 // |
|
454 // TableColumnModelListener methods |
|
455 // |
|
456 |
|
457 @Override |
|
458 public void columnAdded(TableColumnModelEvent e) { |
|
459 revalidateIfOverlayVisible(); |
|
460 } |
|
461 |
|
462 @Override |
|
463 public void columnMarginChanged(ChangeEvent e) { |
|
464 } |
|
465 |
|
466 @Override |
|
467 public void columnMoved(TableColumnModelEvent e) { |
|
468 revalidateIfOverlayVisible(); |
|
469 } |
|
470 |
|
471 @Override |
|
472 public void columnRemoved(TableColumnModelEvent e) { |
|
473 revalidateIfOverlayVisible(); |
|
474 } |
|
475 |
|
476 @Override |
|
477 public void columnSelectionChanged(ListSelectionEvent e) { |
|
478 } |
|
479 |
|
480 // |
|
481 // Private methods |
|
482 // |
|
483 |
|
484 private void revalidateIfOverlayVisible() { |
|
485 if (isOverlayVisible()) { |
|
486 revalidate(); |
|
487 } |
|
488 } |
|
489 }); |
|
490 } |
|
491 |
|
492 // |
|
493 // Static methods |
|
494 // |
|
495 |
|
496 // XXX Unit test -- remove |
|
497 public static void main(String[] args) { |
|
498 JFrame f = new JFrame(); |
|
499 |
|
500 Object[][] data = { |
|
501 new Object[] {"0 0", "0 1", "0 2"}, |
|
502 new Object[] {"1 0", "1 1", "1 2"}, |
|
503 new Object[] {"2 0", "2 1", "2 2"}, |
|
504 new Object[] {"3 0", "3 1", "3 2"}, |
|
505 new Object[] {"4 0", "4 1", "4 2"}, |
|
506 new Object[] {"5 0", "5 1", "5 2"}, |
|
507 new Object[] {"6 0", "6 1", "6 2"}, |
|
508 new Object[] {"7 0", "7 1", "7 2"}, |
|
509 new Object[] {"8 0", "8 1", "8 2"}, |
|
510 new Object[] {"9 0", "9 1", "9 2"}, |
|
511 new Object[] {"10 0", "10 1", "10 2"}, |
|
512 new Object[] {"11 0", "11 1", "11 2"}, |
|
513 new Object[] {"12 0", "12 1", "12 2"}, |
|
514 new Object[] {"13 0", "13 1", "13 2"}, |
|
515 new Object[] {"14 0", "14 1", "14 2"}, |
|
516 new Object[] {"15 0", "15 1", "15 2"}, |
|
517 new Object[] {"16 0", "16 1", "16 2"}, |
|
518 new Object[] {"17 0", "17 1", "17 2"}, |
|
519 new Object[] {"18 0", "18 1", "18 2"}, |
|
520 new Object[] {"19 0", "19 1", "19 2"}, |
|
521 new Object[] {"20 0", "20 1", "20 2"}, |
|
522 new Object[] {"21 0", "21 1", "21 2"}, |
|
523 }; |
|
524 |
|
525 DefaultTableModel model = new DefaultTableModel( |
|
526 data, new String[] {"String", "String", "2"}); |
|
527 |
|
528 final JLabel label = new JLabel("Hello Hello Hello"); |
|
529 label.setBorder(null); |
|
530 // label.setOpaque(true); |
|
531 label.setFont(label.getFont().deriveFont(60f)); |
|
532 label.setForeground(Color.green); |
|
533 label.setBackground(Color.yellow); |
|
534 |
|
535 OverlayTable t = new OverlayTable(model) { |
|
536 @Override |
|
537 public Component getOverlayComponent(int vRow) { |
|
538 return label; |
|
539 } |
|
540 }; |
|
541 t.configureOverlayOnRowSelection(); |
|
542 |
|
543 t.setOverlayShadowEnabled(true); |
|
544 // t.setOverlayBorder(GUIUtil.BORDER_LINE_SHADOW); |
|
545 // t.setOverlayBackground(Color.red); |
|
546 // t.setBackground(Color.blue.brighter()); |
|
547 // t.setStripeColor(Color.orange); |
|
548 // t.setIntercellSpacing(new Dimension(20, 20)); |
|
549 // t.setRowHeight(40); |
|
550 // t.setGridColor(Color.cyan); |
|
551 // t.setShowGrid(true); |
|
552 |
|
553 JScrollPane scroll = new JScrollPane(t); |
|
554 |
|
555 JPanel c = (JPanel)f.getContentPane(); |
|
556 c.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); |
|
557 c.setLayout(new BorderLayout()); |
|
558 c.add(t.getTableHeader(), BorderLayout.NORTH); |
|
559 // c.add(t, BorderLayout.CENTER); |
|
560 c.add(scroll, BorderLayout.CENTER); |
|
561 |
|
562 f.pack(); |
|
563 // f.setSize(500, 500); |
|
564 f.setVisible(true); |
|
565 } |
|
566 } |