1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.ui.swing.components;
20
21 import java.awt.Color;
22 import java.awt.Component;
23 import java.awt.GridBagConstraints;
24 import java.awt.GridBagLayout;
25 import java.awt.Insets;
26 import java.awt.Point;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.event.MouseEvent;
30 import java.awt.event.MouseListener;
31 import java.util.Enumeration;
32 import java.util.EventObject;
33 import java.util.Vector;
34
35 import javax.swing.Icon;
36 import javax.swing.JButton;
37 import javax.swing.JComponent;
38 import javax.swing.JLabel;
39 import javax.swing.JList;
40 import javax.swing.JPanel;
41 import javax.swing.JTable;
42 import javax.swing.JTree;
43 import javax.swing.ListCellRenderer;
44 import javax.swing.SwingUtilities;
45 import javax.swing.UIManager;
46 import javax.swing.border.Border;
47 import javax.swing.border.EmptyBorder;
48 import javax.swing.border.LineBorder;
49 import javax.swing.event.CellEditorListener;
50 import javax.swing.event.ChangeEvent;
51 import javax.swing.table.TableCellEditor;
52 import javax.swing.table.TableCellRenderer;
53 import javax.swing.tree.TreeCellEditor;
54 import javax.swing.tree.TreeCellRenderer;
55
56 /***
57 * A cell renderer that displays icons in addition to text,
58 * and additionally is an editor in case you want to click
59 * the icon to trigger some kind of action.
60 * You probably should override both getStringForContext and
61 * getIconForContext to achieve your desired results.
62 * To receive mouse clicks, set the same instance of the
63 * renderer as the editor for the same component.<br><br>
64 *
65 * One notable addition is that this class is an action event
66 * broadcaster. ActionEvents are broadcast when the mouse is
67 * clicked on the button with an action event containing a
68 * user-configurable string that defaults to CLICKED. <br><br>
69 *
70 * The renderer itself can be used as a JComponent if
71 * you need something like a JLabel that allows you to click
72 * on the icon. You will want to call setIcon and setText
73 * to configure the component since the renderer method would
74 * not be called. (If you add an instance of the renderer
75 * to a container, you cannnot use the same instance as an
76 * editor in a table, tree, or list.)
77 *
78 * @author michael@mpowers.net
79 * @version $Revision: 904 $
80 * $Date: 2006-02-18 23:19:05 +0000 (Sat, 18 Feb 2006) $
81 */
82 public class IconCellRenderer extends JPanel
83 implements TableCellRenderer, TableCellEditor,
84 TreeCellRenderer, TreeCellEditor, ListCellRenderer,
85 Runnable, ActionListener, MouseListener
86 {
87 public static final String CLICKED = "CLICKED";
88
89 /***
90 * The panel that is re-used to render everything.
91 * This is returned by getRendererForContext.
92 */
93 protected JPanel rendererPanel;
94 protected JLabel rendererLabel;
95 protected JButton rendererButton;
96
97 /***
98 * The panel that is used to receive mouse clicks.
99 * It must be a different component from rendererPanel.
100 * This is returned by getEditorForContext.
101 */
102 protected JPanel editorPanel;
103 protected JLabel editorLabel;
104 protected JButton editorButton;
105
106 private Object lastKnownValue;
107 private JComponent lastKnownComponent;
108
109
110 private Border noFocusBorder;
111 private Border treeFocusBorder;
112 private Color unselectedForeground;
113 private Color unselectedBackground;
114
115 private Vector actionListeners;
116 private String actionCommand;
117 private Vector cellEditorListeners;
118
119 private boolean editable;
120 private boolean clickable;
121
122 /***
123 * Default constructor.
124 */
125 public IconCellRenderer()
126 {
127 editable = true;
128 clickable = true;
129
130 noFocusBorder = new EmptyBorder(1, 1, 1, 1);
131 treeFocusBorder = new LineBorder(
132 UIManager.getColor("Tree.selectionBorderColor") );
133 setActionCommand( CLICKED );
134
135 rendererPanel = new JPanel();
136 rendererPanel.setLayout( new GridBagLayout() );
137
138 editorPanel = this;
139 editorPanel.setLayout( new GridBagLayout() );
140
141
142 GridBagConstraints imageConstraints = new GridBagConstraints();
143 imageConstraints.gridx = 0;
144 GridBagConstraints labelConstraints = new GridBagConstraints();
145 labelConstraints.fill = GridBagConstraints.HORIZONTAL;
146 labelConstraints.gridx = 1;
147 labelConstraints.weightx = 1.0;
148 labelConstraints.ipadx = 1;
149 labelConstraints.insets = new Insets( 0, 1, 0, 0 );
150
151
152
153
154
155
156 editorPanel.addMouseListener( this );
157
158 rendererLabel = new JLabel();
159 rendererLabel.setOpaque( false );
160 rendererPanel.add( rendererLabel, labelConstraints );
161
162 editorLabel = new JLabel();
163 editorLabel.setText( "" );
164 editorLabel.setOpaque( false );
165 editorPanel.add( editorLabel, labelConstraints );
166
167 unselectedForeground = rendererLabel.getForeground();
168 unselectedBackground = rendererLabel.getBackground();
169
170 rendererButton = new JButton();
171 rendererButton.setBorder( null );
172 rendererButton.setBorderPainted( false );
173 rendererButton.setContentAreaFilled( false );
174 rendererButton.setFocusPainted( false );
175 rendererButton.setMargin( new Insets( 0, 0, 0, 0 ) );
176 rendererPanel.add( rendererButton, imageConstraints );
177
178 editorButton = new JButton();
179 editorButton.setEnabled( clickable );
180 editorButton.setIcon( null );
181 editorButton.setBorder( null );
182 editorButton.setBorderPainted( false );
183 editorButton.setContentAreaFilled( false );
184 editorButton.setFocusPainted( false );
185 editorButton.setMargin( new Insets( 0, 0, 0, 0 ) );
186 editorPanel.add( editorButton, imageConstraints );
187
188 editorButton.addActionListener( this );
189
190
191
192
193 editorLabel.addMouseListener( this );
194 editorButton.addMouseListener( this );
195 }
196
197 /***
198 * Returns the text string currently displayed in the editor component.
199 */
200 public String getText()
201 {
202 return editorLabel.getText();
203 }
204
205 /***
206 * Sets the text string displayed in the editor component.
207 * Default is an empty string.
208 */
209 public void setText( String aString )
210 {
211 editorLabel.setText( aString );
212 }
213
214 /***
215 * Returns the icon currently displayed in the editor component.
216 */
217 public Icon getIcon()
218 {
219 return editorButton.getIcon();
220 }
221
222 /***
223 * Sets the icon currently displayed in the editor component.
224 * Default is null.
225 */
226 public void setIcon( Icon anIcon )
227 {
228 editorButton.setIcon( anIcon );
229 if ( !isClickable() )
230 {
231 editorButton.setDisabledIcon( anIcon );
232 }
233 }
234
235 /***
236 * Returns whether the editor component's label text is editable.
237 */
238 public boolean isEditable()
239 {
240 return editable;
241 }
242
243 /***
244 * Sets whether the editor component's label text is editable.
245 * Default is true. Editable text is not yet implemented.
246 */
247 public void setEditable( boolean isEditable )
248 {
249 editable = isEditable;
250 }
251
252 /***
253 * Returns whether the editor component's icon is clickable.
254 */
255 public boolean isClickable()
256 {
257 return clickable;
258 }
259
260 /***
261 * Sets whether the editor component's icon is clickable.
262 * Default is true.
263 */
264 public void setClickable( boolean isClickable )
265 {
266 clickable = isClickable;
267 editorButton.setEnabled( clickable );
268 }
269
270 /***
271 * Returns the component from getRendererForContext.
272 */
273 public Component getListCellRendererComponent(JList list,
274 Object value,
275 int index,
276 boolean isSelected,
277 boolean cellHasFocus)
278 {
279 lastKnownComponent = list;
280 return getRendererForContext(
281 list, value, index, 0, isSelected, cellHasFocus, false, true );
282 }
283
284 /***
285 * Returns the component from getRendererForContext.
286 */
287 public Component getTableCellRendererComponent(JTable table, Object value,
288 boolean isSelected, boolean hasFocus, int row, int column)
289 {
290 lastKnownComponent = table;
291 return getRendererForContext(
292 table, value, row, column, isSelected, hasFocus, false, true );
293 }
294
295 /***
296 * Returns the component from getRendererForContext.
297 */
298 public Component getTreeCellRendererComponent(JTree tree,
299 Object value,
300 boolean selected,
301 boolean expanded,
302 boolean leaf,
303 int row,
304 boolean hasFocus)
305 {
306 lastKnownComponent = tree;
307 return getRendererForContext(
308 tree, value, row, 0, selected, hasFocus, expanded, leaf );
309 }
310
311 /***
312 * Returns getEditorForContext with the same parameters with hasFocus true.
313 */
314 public Component getTableCellEditorComponent(JTable table,
315 Object value, boolean isSelected, int row, int column)
316 {
317 lastKnownValue = value;
318 lastKnownComponent = table;
319 return getEditorForContext(
320 table, value, row, column, isSelected, true, false, true );
321 }
322
323 /***
324 * Returns the component from getEditorForContext with hasFocus true.
325 */
326 public Component getTreeCellEditorComponent(JTree tree,
327 Object value,
328 boolean isSelected,
329 boolean expanded,
330 boolean leaf,
331 int row)
332 {
333
334
335 lastKnownValue = value;
336 lastKnownComponent = tree;
337
338 return getEditorForContext(
339 tree, value, row, 0, isSelected, true, expanded, leaf );
340 }
341
342 /***
343 * This default implementation returns a JPanel that is configured by
344 * calling configureComponentForContext.
345 * @return An component that is used to render content.
346 */
347 public Component getRendererForContext(
348 JComponent container, Object value,
349 int row, int column,
350 boolean isSelected, boolean hasFocus,
351 boolean isExpanded, boolean isLeaf )
352 {
353
354
355 configureComponentForContext( rendererPanel, rendererButton, rendererLabel,
356 container, value, row, column,
357 isSelected, hasFocus, isExpanded, isLeaf );
358 return rendererPanel;
359 }
360
361 /***
362 * This method returns a separate component that should be visually
363 * identical to the renderer component. We can't simply reuse the
364 * renderer component because the renderer is still used to paint
365 * the table while the editor component is displayed. Clicks are
366 * received on this component.
367 * This default implementation returns a JPanel that is configured by
368 * calling configureComponentForContext.
369 * @return A component used to receive clicks on the cell.
370 */
371 public Component getEditorForContext(
372 JComponent container, Object value,
373 int row, int column,
374 boolean isSelected, boolean hasFocus,
375 boolean isExpanded, boolean isLeaf )
376 {
377 configureComponentForContext( editorPanel, editorButton, editorLabel,
378 container, value, row, column,
379 true, hasFocus, isExpanded, isLeaf );
380
381 return editorPanel;
382 }
383
384 /***
385 * Called to configure components
386 */
387 protected void configureComponentForContext(
388 JPanel component, JButton iconButton, JLabel label,
389 JComponent container, Object value,
390 int row, int column,
391 boolean isSelected, boolean hasFocus,
392 boolean isExpanded, boolean isLeaf )
393 {
394 if (hasFocus)
395 {
396 if ( container instanceof JTable )
397 {
398 component.setBorder(
399 UIManager.getBorder("Table.focusCellHighlightBorder") );
400 }
401 else
402 {
403 component.setBorder( noFocusBorder );
404 }
405
406 if ( container instanceof JTree )
407 {
408 label.setBorder( treeFocusBorder );
409 }
410 else
411 {
412 label.setBorder( noFocusBorder );
413 }
414 }
415 else
416 {
417 label.setBorder(noFocusBorder);
418 component.setBorder(noFocusBorder);
419 }
420
421 if (isSelected)
422 {
423 if ( container instanceof JTree )
424 {
425 label.setOpaque( true );
426 label.setForeground(UIManager.getColor("Tree.selectionForeground"));
427 label.setBackground(UIManager.getColor("Tree.selectionBackground"));
428 component.setBackground(container.getBackground());
429 }
430 else if ( container instanceof JTable )
431 {
432 label.setOpaque( false );
433 label.setForeground( ((JTable)container).getSelectionForeground() );
434 component.setBackground(((JTable)container).getSelectionBackground());
435 }
436 else
437 {
438 label.setOpaque( false );
439 label.setForeground(UIManager.getColor("Table.selectionForeground"));
440 component.setBackground(UIManager.getColor("Table.selectionBackground"));
441 }
442 }
443 else
444 {
445 label.setOpaque( false );
446 label.setForeground(container.getForeground());
447 component.setBackground(container.getBackground());
448 }
449
450 label.setFont(container.getFont());
451
452 Icon icon = getIconForContext(
453 container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf );
454 iconButton.setIcon( icon );
455 if ( !isClickable() )
456 {
457 iconButton.setDisabledIcon( icon );
458 }
459
460 String text = getStringForContext(
461 container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf );
462
463 if ( ( text == null ) || ( "".equals( text ) ) )
464 {
465 if ( ! label.getText().equals( "" ) )
466 label.setText( "" );
467 }
468 else
469 {
470 if ( ! label.getText().equals( text ) )
471 label.setText( text );
472 }
473 }
474
475 /***
476 * Override this method to provide an icon for the renderer.
477 * This default implementation returns null.
478 * @return An icon to be displayed in the cell, or null to omit the
479 * icon from the cell.
480 */
481 public Icon getIconForContext(
482 JComponent container, Object value,
483 int row, int column,
484 boolean isSelected, boolean hasFocus,
485 boolean isExpanded, boolean isLeaf )
486 {
487 return null;
488 }
489
490 /***
491 * Override this method to provide a string for the renderer.
492 * This default implementation returns toString on the value parameter,
493 * or null if the value is null.
494 * @return A string to be displayed in the cell.
495 */
496 public String getStringForContext(
497 JComponent container, Object value,
498 int row, int column,
499 boolean isSelected, boolean hasFocus,
500 boolean isExpanded, boolean isLeaf )
501 {
502 if ( value == null ) return null;
503 return value.toString();
504 }
505
506 /***
507 * Adds the specified listener to the list of listeners
508 * to be notified when the button receives a click.
509 */
510 public void addActionListener( ActionListener aListener )
511 {
512 if ( actionListeners == null )
513 {
514 actionListeners = new Vector( 2 );
515 }
516 actionListeners.add( aListener );
517 }
518
519 /***
520 * Removes the specified listener from the list of listeners
521 * to be notified when the button receives a click.
522 */
523 public void removeActionListener( ActionListener aListener )
524 {
525 actionListeners.remove( aListener );
526 }
527
528 /***
529 * Broadcasts the specified action event to all listeners.
530 */
531 protected void fireActionEvent( ActionEvent anActionEvent )
532 {
533 if ( actionListeners == null ) return;
534
535 Enumeration e = actionListeners.elements();
536 while ( e.hasMoreElements() )
537 {
538 ((ActionListener)e.nextElement()).actionPerformed( anActionEvent );
539 }
540 }
541
542 /***
543 * Returns the action command broadcast when this icon
544 * receives a click. Defaults to CLICKED.
545 */
546 public String getActionCommand()
547 {
548 return actionCommand;
549 }
550
551 /***
552 * Sets the action command broadcast when this table
553 * receives a double click.
554 */
555 public void setActionCommand( String anActionCommand )
556 {
557 actionCommand = anActionCommand;
558 }
559
560
561
562 /***
563 * Returns lastKnownValue, although this should not be called.
564 */
565 public Object getCellEditorValue()
566 {
567 return lastKnownValue;
568 }
569
570 /***
571 * Returns true.
572 */
573 public boolean isCellEditable(EventObject anEvent)
574 {
575 return true;
576 }
577
578 /***
579 * Returns true.
580 */
581 public boolean shouldSelectCell(EventObject anEvent)
582 {
583 return true;
584 }
585
586 /***
587 * Fires an editing stopped event and returns true.
588 */
589 public boolean stopCellEditing()
590 {
591 ChangeEvent event = new ChangeEvent( this );
592 if ( cellEditorListeners != null )
593 {
594
595 Enumeration e = cellEditorListeners.elements();
596 while ( e.hasMoreElements() )
597 {
598
599 ((CellEditorListener)e.nextElement()).editingCanceled( event );
600 }
601 }
602 lastKnownComponent = null;
603 return true;
604 }
605
606 /***
607 * Fires an editing cancelled event and returns true.
608 */
609 public void cancelCellEditing()
610 {
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629 ChangeEvent event = new ChangeEvent( this );
630 if ( cellEditorListeners == null ) return;
631
632 Enumeration e = cellEditorListeners.elements();
633
634 while ( e.hasMoreElements() )
635 {
636 ((CellEditorListener)e.nextElement()).editingCanceled( event );
637 }
638
639
640 lastKnownComponent = null;
641 }
642
643 /***
644 * Adds the specified listener to the list of listeners
645 * to be notified when the table receives a double click.
646 */
647 public void addCellEditorListener( CellEditorListener aListener )
648 {
649 if ( cellEditorListeners == null )
650 {
651 cellEditorListeners = new Vector( 2 );
652 }
653 cellEditorListeners.add( aListener );
654 }
655
656 /***
657 * Removes the specified listener from the list of listeners
658 * to be notified when the table receives a double click.
659 */
660 public void removeCellEditorListener( CellEditorListener aListener )
661 {
662 cellEditorListeners.remove( aListener );
663 }
664
665
666
667 /***
668 * Puts ourself on the end of the event queue for
669 * firing our action event to all listeners.
670 */
671 public void actionPerformed( ActionEvent evt )
672 {
673
674
675
676
677
678 SwingUtilities.invokeLater( this );
679 }
680
681
682
683 /***
684 * Fires the action event to all listeners.
685 * This is triggered by a click on the icon.
686 */
687 public void run()
688 {
689 fireActionEvent( new ActionEvent( this, 0, getActionCommand() ) );
690 }
691
692
693
694 /***
695 * Passes through editor mouse clicks to last known component.
696 * (left click only)
697 */
698 public void mouseClicked(MouseEvent e)
699 {
700 if(lastKnownComponent != null){
701 Object source = e.getSource();
702 if(source != null)
703 {
704 if(source == editorPanel)
705 {
706 lastKnownComponent.dispatchEvent(
707 SwingUtilities.convertMouseEvent(
708 editorPanel, e, lastKnownComponent ) );
709
710 }
711 else if(source == editorLabel)
712 {
713 lastKnownComponent.dispatchEvent(
714 SwingUtilities.convertMouseEvent(
715 editorLabel, e, lastKnownComponent ) );
716 }
717
718 else if(source == editorButton)
719 {
720 lastKnownComponent.dispatchEvent(
721 SwingUtilities.convertMouseEvent(
722 editorButton, e, lastKnownComponent ) );
723 }
724 }
725 }
726 }
727
728 /***
729 * Passes through editor right-mouse (popup trigger) mouse events to last known component.
730 * Needed for possible displaying of popup menus on right click
731 */
732 public void mousePressed(MouseEvent e)
733 {
734 if ( e.isPopupTrigger() )
735 {
736 if(lastKnownComponent != null)
737 {
738 Object source = e.getSource();
739 if(source != null)
740 {
741 if(source == editorPanel)
742 {
743 lastKnownComponent.dispatchEvent(
744 SwingUtilities.convertMouseEvent(
745 editorPanel, e, lastKnownComponent ) );
746 }
747 else if(source == editorLabel)
748 {
749 lastKnownComponent.dispatchEvent(
750 SwingUtilities.convertMouseEvent(
751 editorLabel, e, lastKnownComponent ) );
752 }
753
754 else if(source == editorButton)
755 {
756 lastKnownComponent.dispatchEvent(
757 SwingUtilities.convertMouseEvent(
758 editorButton, e, lastKnownComponent ) );
759 }
760 }
761 }
762 }
763 }
764
765 /***
766 * Does nothing.
767 */
768 public void mouseReleased(MouseEvent e)
769 {
770 if ( e.isPopupTrigger() )
771 {
772 if(lastKnownComponent != null){
773
774 Object source = e.getSource();
775 if(source != null)
776 {
777 if(source == editorPanel)
778 {
779 lastKnownComponent.dispatchEvent(
780 SwingUtilities.convertMouseEvent(
781 editorPanel, e, lastKnownComponent ) );
782 }
783
784 else if(source == editorLabel)
785 {
786 lastKnownComponent.dispatchEvent(
787 SwingUtilities.convertMouseEvent(
788 editorLabel, e, lastKnownComponent ) );
789 }
790
791 else if(source == editorButton)
792 {
793 lastKnownComponent.dispatchEvent(
794 SwingUtilities.convertMouseEvent(
795 editorButton, e, lastKnownComponent ) );
796 }
797 }
798 }
799 }
800 }
801
802 /***
803 * Does nothing.
804 */
805 public void mouseEntered(MouseEvent e)
806 {
807 }
808
809 /***
810 * Cancels cell editing.
811 */
812 public void mouseExited(MouseEvent e)
813 {
814 Object source = e.getSource();
815 if(source != null && source instanceof JComponent){
816
817
818
819 Point convertedPoint = SwingUtilities.convertPoint((JComponent) source, e.getPoint(), editorPanel);
820
821
822 if(!editorPanel.contains(convertedPoint)){
823
824
825 cancelCellEditing();
826 }
827 }
828 }
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845 }