001    /*
002     * $Id: ColumnControlButton.java 2739 2008-02-20 20:06:39Z kleopatra $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx.table;
023    
024    import java.awt.ComponentOrientation;
025    import java.awt.Dimension;
026    import java.awt.Insets;
027    import java.awt.event.ActionEvent;
028    import java.awt.event.ItemEvent;
029    import java.beans.PropertyChangeEvent;
030    import java.beans.PropertyChangeListener;
031    import java.util.ArrayList;
032    import java.util.Collections;
033    import java.util.List;
034    
035    import javax.swing.AbstractAction;
036    import javax.swing.Action;
037    import javax.swing.Icon;
038    import javax.swing.JButton;
039    import javax.swing.JComboBox;
040    import javax.swing.JComponent;
041    import javax.swing.JMenuItem;
042    import javax.swing.JPopupMenu;
043    import javax.swing.SwingUtilities;
044    import javax.swing.UIManager;
045    import javax.swing.event.ChangeEvent;
046    import javax.swing.event.ListSelectionEvent;
047    import javax.swing.event.TableColumnModelEvent;
048    import javax.swing.event.TableColumnModelListener;
049    import javax.swing.plaf.UIResource;
050    import javax.swing.table.TableColumn;
051    import javax.swing.table.TableColumnModel;
052    
053    import org.jdesktop.swingx.JXTable;
054    import org.jdesktop.swingx.action.AbstractActionExt;
055    import org.jdesktop.swingx.action.ActionContainerFactory;
056    import org.jdesktop.swingx.plaf.ColumnControlButtonAddon;
057    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
058    
059    /**
060     * A component to allow interactive customization of <code>JXTable</code>'s
061     * columns. 
062     * It's main purpose is to allow toggling of table columns' visibility. 
063     * Additionally, arbitrary configuration actions can be exposed.
064     * <p>
065     * 
066     * This component is installed in the <code>JXTable</code>'s
067     * trailing corner, if enabled:
068     * 
069     * <pre><code>
070     * table.setColumnControlVisible(true);
071     * </code></pre>
072     * 
073     * From the perspective of a <code>JXTable</code>, the component's behaviour is
074     * opaque. Typically, the button's action is to popup a component for user
075     * interaction. <p>
076     * 
077     * This class is responsible for handling/providing/updating the lists of
078     * actions and to keep all action's state in synch with Table-/Column state. 
079     * The visible behaviour of the popup is delegated to a
080     * <code>ColumnControlPopup</code>. <p>
081     * 
082     * @see TableColumnExt
083     * @see TableColumnModelExt
084     * @see JXTable#setColumnControl
085     * 
086     */
087    public class ColumnControlButton extends JButton {
088        
089        // JW: really want to extend? for builders?
090        /** Marker to auto-recognize actions which should be added to the popup. */
091        public static final String COLUMN_CONTROL_MARKER = "column.";
092        
093        /** the key for looking up the control's icon in the UIManager. Typically, it's LAF dependent. */
094        public static final String COLUMN_CONTROL_BUTTON_ICON_KEY = "ColumnControlButton.actionIcon";
095        
096        /** the key for looking up the control's margin in the UIManager. Typically, it's LAF dependent. */
097        public static final String COLUMN_CONTROL_BUTTON_MARGIN_KEY = "ColumnControlButton.margin";
098        static {
099            LookAndFeelAddons.contribute(new ColumnControlButtonAddon());
100        }
101    
102        /** exposed for testing. */
103        protected ColumnControlPopup popup;
104        // TODO: the table reference is a potential leak?
105        /** The table which is controlled by this. */
106        private JXTable table;
107        /** Listener for table property changes. */
108        private PropertyChangeListener tablePropertyChangeListener;
109        /** Listener for table's columnModel. */
110        TableColumnModelListener columnModelListener;
111        /** the list of actions for column menuitems.*/
112        private List<ColumnVisibilityAction> columnVisibilityActions;
113    
114        
115        /**
116         * Creates a column control button for the table. Uses the default
117         * icon as provided by the addon.
118         * 
119         * @param table  the <code>JXTable</code> controlled by this component
120         */
121        public ColumnControlButton(JXTable table) {
122            this(table, null);
123        }
124    
125        /**
126         * Creates a column control button for the table. The button
127         * uses the given icon and has no text.
128         * @param table  the <code>JXTable</code> controlled by this component
129         * @param icon the <code>Icon</code> to show
130         */
131        public ColumnControlButton(JXTable table, Icon icon) {
132            super();
133            init();
134            // JW: icon LF dependent?
135            setAction(createControlAction(icon));
136            updateActionUI();
137            updateButtonUI();
138            installTable(table);
139        }
140    
141        
142        @Override
143        public void updateUI() {
144            super.updateUI();
145            // JW: icon may be LF dependent
146            updateActionUI();
147            updateButtonUI();
148            getColumnControlPopup().updateUI();
149        }
150    
151        /**
152         * Updates this button's properties provided by the LAF.
153         * Here: overwrites the action's small_icon with the icon from the ui if the current
154         *   icon is null or a UIResource.
155         */
156        protected void updateButtonUI() {
157            if ((getMargin() == null) || (getMargin() instanceof UIResource)) {
158                Insets insets = UIManager.getInsets(COLUMN_CONTROL_BUTTON_MARGIN_KEY);
159                setMargin(insets); 
160            }
161        }
162        
163        /**
164         * Updates the action properties provided by the LAF.
165         * Here: overwrites the action's small_icon with the icon from the ui if the current
166         *   icon is null or a UIResource.
167         */
168        protected void updateActionUI() {
169            if (getAction() == null) return;
170            Icon icon = (Icon) getAction().getValue(Action.SMALL_ICON);
171            if ((icon == null) || (icon instanceof UIResource)) {
172                icon = UIManager.getIcon(COLUMN_CONTROL_BUTTON_ICON_KEY);
173                getAction().putValue(Action.SMALL_ICON, icon);
174            }
175        }
176    
177        /** 
178         * Toggles the popup component's visibility. This method is
179         * called by this control's default action. <p>
180         * 
181         * Here: delegates to getControlPopup().
182         */ 
183        public void togglePopup() {
184            getColumnControlPopup().toggleVisibility(this);
185        }
186    
187        @Override
188        public void applyComponentOrientation(ComponentOrientation o) {
189            super.applyComponentOrientation(o);
190            getColumnControlPopup().applyComponentOrientation(o);
191        }
192    
193       
194    //-------------------------- Action in synch with column properties
195        /**
196         * A specialized <code>Action</code> which takes care of keeping in synch with
197         * TableColumn state.
198         * 
199         * NOTE: client must call releaseColumn if this action is no longer needed!
200         * 
201         */
202        public class ColumnVisibilityAction extends AbstractActionExt {
203    
204            private TableColumn column;
205    
206            private PropertyChangeListener columnListener;
207    
208            /** flag to distinguish selection changes triggered by
209             *  column's property change from those triggered by
210             *  user interaction. Hack around #212-swingx.
211             */
212            private boolean fromColumn;
213    
214            /**
215             * Creates a action synched to the table column.
216             * 
217             * @param column the <code>TableColumn</code> to keep synched to.
218             */
219            public ColumnVisibilityAction(TableColumn column) {
220                super((String) null);
221                setStateAction();
222                installColumn(column);
223            }
224    
225            /**
226             * Releases all references to the synched <code>TableColumn</code>. 
227             * Client code must call this method if the
228             * action is no longer needed. After calling this action must not be
229             * used any longer.
230             */
231            public void releaseColumn() {
232                column.removePropertyChangeListener(columnListener);
233                column = null;
234            }
235            
236            /**
237             * Returns true if the action is enabled. Returns
238             * true only if the action is enabled and the table
239             * column can be controlled.
240             * 
241             * @return true if the action is enabled, false otherwise
242             * @see #canControlColumn()
243             */
244            @Override
245            public boolean isEnabled() {
246                return super.isEnabled() && canControlColumn(); 
247            }
248            
249            /**
250             * Returns flag to indicate if column's visibility can
251             * be controlled. Minimal requirement is that column is of type 
252             * <code>TableColumnExt</code>. 
253             * 
254             * @return boolean to indicate if columns's visibility can be controlled.
255             */
256            protected boolean canControlColumn() {
257                // JW: should have direction? control is from action to column, the
258                // other way round should be guaranteed always
259                return (column instanceof TableColumnExt);
260            }
261    
262            @Override
263            public void itemStateChanged(final ItemEvent e) {
264                if (canControlColumn()) {
265                    if ((e.getStateChange() == ItemEvent.DESELECTED)
266                            //JW: guarding against 1 leads to #212-swingx: setting
267                            // column visibility programatically fails if
268                            // the current column is the second last visible
269                            // guarding against 0 leads to hiding all columns
270                            // by deselecting the menu item. 
271                            && (table.getColumnCount() <= 1)
272                            // JW Fixed #212: basically implemented Rob's idea to distinguish
273                            // event sources instead of unconditionally reselect
274                            // not entirely sure if the state transitions are completely
275                            // defined but all related tests are passing now.
276                            && !fromColumn) {
277                        reselect();
278                    } else {
279                        setSelected(e.getStateChange() == ItemEvent.SELECTED);
280                    }
281                }
282            }
283    
284            
285            @Override
286            public synchronized void setSelected(boolean newValue) {
287                super.setSelected(newValue);
288                if (canControlColumn()) {
289                    ((TableColumnExt) column).setVisible(newValue);
290                }
291            }
292    
293            /**
294             * Does nothing. Synch from action state to TableColumn state
295             * is done in itemStateChanged.
296             */
297            public void actionPerformed(ActionEvent e) {
298    
299            }
300            
301            /**
302             * Synchs selected property to visible. This
303             * is called on change of tablecolumn's <code>visible</code> property.
304             * 
305             * @param visible column visible state to synch to.
306             */
307            private void updateFromColumnVisible(boolean visible) {
308    //            /*boolean*/ visible = true;
309    //            if (canControlColumn()) {
310    //                visible = ((TableColumnExt) column).isVisible();
311    //            }
312                fromColumn = true;
313                setSelected(visible);
314                fromColumn = false;
315            }
316            
317            /**
318             * Synchs name property to value. This is called on change of 
319             * tableColumn's <code>headerValue</code> property.
320             * 
321             * @param value
322             */
323            private void updateFromColumnHeader(Object value) {
324                setName(String.valueOf(value));
325            }
326    
327            /**
328             * Enforces selected to <code>true</code>. Called if user interaction
329             * tried to de-select the last single visible column.
330             *
331             */
332            private void reselect() {
333                firePropertyChange("selected", null, Boolean.TRUE);
334            }
335    
336            // -------------- init
337            private void installColumn(TableColumn column) {
338                this.column = column;
339                column.addPropertyChangeListener(getColumnListener());
340                updateFromColumnHeader(column.getHeaderValue());
341                // #429-swing: actionCommand must be string
342                if (column.getIdentifier() != null) {
343                    setActionCommand(column.getIdentifier().toString());
344                }
345                boolean visible = (column instanceof TableColumnExt) ? 
346                        ((TableColumnExt) column).isVisible() : true;
347                updateFromColumnVisible(visible);
348            }
349    
350            /**
351             * Returns the listener to column's property changes. The listener
352             * is created lazily if necessary.
353             * 
354             * @return the <code>PropertyChangeListener</code> listening to 
355             *   <code>TableColumn</code>'s property changes, guaranteed to be 
356             *   not <code>null</code>.
357             */
358            protected PropertyChangeListener getColumnListener() {
359                if (columnListener == null) {
360                    columnListener = createPropertyChangeListener();
361                }
362                return columnListener;
363            }
364    
365            /**
366             * Creates and returns the listener to column's property changes.
367             * Subclasses are free to roll their own.
368             * <p>
369             * Implementation note: this listener reacts to column's
370             * <code>visible</code> and <code>headerValue</code> properties and
371             * calls the respective <code>updateFromXX</code> methodes.
372             * 
373             * @return the <code>PropertyChangeListener</code> to use with the
374             *         column
375             */
376            protected PropertyChangeListener createPropertyChangeListener() {
377                return new PropertyChangeListener() {
378                    public void propertyChange(PropertyChangeEvent evt) {
379                        if ("visible".equals(evt.getPropertyName())) {
380                            updateFromColumnVisible((Boolean) evt.getNewValue());
381                        } else if ("headerValue".equals(evt.getPropertyName())) {
382                            updateFromColumnHeader(evt.getNewValue());
383                        }
384                    }
385                };
386            }
387        }
388    
389        // ---------------------- the popup
390    
391        /**
392         * A default implementation of ColumnControlPopup. 
393         * It uses a JPopupMenu with 
394         * MenuItems corresponding to the Actions as 
395         * provided by the ColumnControlButton.
396         * 
397         * 
398         */
399        public class DefaultColumnControlPopup implements ColumnControlPopup {
400            private JPopupMenu popupMenu;
401    
402            //------------------ public methods to control visibility status
403            
404            /** 
405             * @inheritDoc
406             * 
407             */
408            public void updateUI() {
409                SwingUtilities.updateComponentTreeUI(getPopupMenu());
410            }
411    
412            /** 
413             * @inheritDoc
414             * 
415             */
416            public void toggleVisibility(JComponent owner) {
417                JPopupMenu popupMenu = getPopupMenu();
418                if (popupMenu.isVisible()) {
419                    popupMenu.setVisible(false);
420                } else if (popupMenu.getComponentCount() > 0) {
421                    Dimension buttonSize = owner.getSize();
422                    int xPos = owner.getComponentOrientation().isLeftToRight() ? buttonSize.width
423                            - popupMenu.getPreferredSize().width
424                            : 0;
425                    popupMenu.show(owner,
426                            // JW: trying to allow popup without CCB showing
427                            // weird behaviour
428    //                        owner.isShowing()? owner : null, 
429                            xPos, buttonSize.height);
430                }
431    
432            }
433    
434            /** 
435             * @inheritDoc
436             * 
437             */
438            public void applyComponentOrientation(ComponentOrientation o) {
439                getPopupMenu().applyComponentOrientation(o);
440    
441            }
442    
443            //-------------------- public methods to manipulate popup contents.
444            
445            /** 
446             * @inheritDoc
447             * 
448             */
449            public void removeAll() {
450                getPopupMenu().removeAll();
451            }
452    
453    
454            /** 
455             * @inheritDoc
456             * 
457             */
458            public void addVisibilityActionItems(
459                    List<? extends AbstractActionExt> actions) {
460                addItems(new ArrayList<Action>(actions));
461    
462            }
463    
464    
465            /** 
466             * @inheritDoc
467             * 
468             */
469            public void addAdditionalActionItems(List<? extends Action> actions) {
470                if (actions.size() == 0)
471                    return;
472                // JW: this is a reference to the enclosing class 
473                // prevents to make this implementation static
474                // Hmmm...any way around?
475                if (canControl()) {
476                    addSeparator();
477                }
478                addItems(actions);
479            }
480            
481            //--------------------------- internal helpers to manipulate popups content
482            
483            /**
484             * Here: creates and adds a menuItem to the popup for every 
485             * Action in the list. Does nothing if 
486             * if the list is empty.
487             * 
488             * PRE: actions != null.
489             * 
490             * @param actions a list containing the actions to add to the popup.
491             *        Must not be null.
492             * 
493             */
494            protected void addItems(List<? extends Action> actions) {
495                ActionContainerFactory factory = new ActionContainerFactory(null);
496                for (Action action : actions) {
497                    addItem(factory.createMenuItem(action));
498                }
499    
500            }
501            
502            /**
503             * adds a separator to the popup.
504             *
505             */
506            protected void addSeparator() {
507                getPopupMenu().addSeparator();
508            }
509    
510            /**
511             * 
512             * @param item the menuItem to add to the popup.
513             */
514            protected void addItem(JMenuItem item) {
515                getPopupMenu().add(item);
516            }
517    
518            /**
519             * 
520             * @return the popup to add menuitems. Guaranteed to be != null.
521             */
522            protected JPopupMenu getPopupMenu() {
523                if (popupMenu == null) {
524                    popupMenu = new JPopupMenu();
525                }
526                return popupMenu;
527            }
528    
529        }
530    
531    
532        /**
533         * Returns to popup component for user interaction. Lazily 
534         * creates the component if necessary.
535         * 
536         * @return the ColumnControlPopup for showing the items, guaranteed
537         *   to be not <code>null</code>.
538         * @see #createColumnControlPopup()  
539         */
540        protected ColumnControlPopup getColumnControlPopup() {
541            if (popup == null) {
542                popup = createColumnControlPopup();
543            }
544            return popup;
545        }
546    
547        /**
548         * Factory method to return a <code>ColumnControlPopup</code>.
549         * Subclasses can override to hook custom implementations.
550         * 
551         * @return the <code>ColumnControlPopup</code> used.
552         */
553        protected ColumnControlPopup createColumnControlPopup() {
554            return new DefaultColumnControlPopup();
555        }
556    
557    
558    //-------------------------- updates from table propertyChangelistnere
559        
560        /**
561         * Adjusts internal state after table's column model property has changed.
562         * Handles cleanup of listeners to the old/new columnModel (Note, that
563         * it listens to the column model only if it can control column visibility).
564         * Updates content of popup.
565         * 
566         * @param oldModel the old <code>TableColumnModel</code> we had been listening to.
567         */
568        protected void updateFromColumnModelChange(TableColumnModel oldModel) {
569            if (oldModel != null) {
570                oldModel.removeColumnModelListener(columnModelListener);
571            }
572            populatePopup();
573            if (canControl()) {
574                table.getColumnModel().addColumnModelListener(getColumnModelListener());
575            }
576        }
577        
578        /**
579         * Synchs this button's enabled with table's enabled.
580         *
581         */
582        protected void updateFromTableEnabledChanged() {
583            getAction().setEnabled(table.isEnabled());
584            
585        }
586        /**
587         * Method to check if we can control column visibility POST: if true we can
588         * be sure to have an extended TableColumnModel
589         * 
590         * @return boolean to indicate if controlling the visibility state is
591         *   possible. 
592         */
593        protected boolean canControl() {
594            return table.getColumnModel() instanceof TableColumnModelExt;
595        }
596     
597    //  ------------------------ updating the popup
598        /**
599         * Populates the popup from scratch.
600         * 
601         * If applicable, creates and adds column visibility actions. Always adds
602         * additional actions.
603         */
604        protected void populatePopup() {
605            clearAll();
606            if (canControl()) {
607                createVisibilityActions();
608                addVisibilityActionItems();
609            }
610            addAdditionalActionItems();
611        }
612    
613        /**
614         * 
615         * removes all components from the popup, making sure to release all
616         * columnVisibility actions.
617         * 
618         */
619        protected void clearAll() {
620            clearColumnVisibilityActions();
621            getColumnControlPopup().removeAll();
622        }
623    
624    
625        /**
626         * Releases actions and clears list of actions.
627         * 
628         */
629        protected void clearColumnVisibilityActions() {
630            if (columnVisibilityActions == null)
631                return;
632            for (ColumnVisibilityAction action : columnVisibilityActions) {
633                action.releaseColumn();
634            }
635            columnVisibilityActions.clear();
636        }
637    
638       
639        /**
640         * Adds visibility actions into the popup view.
641         * 
642         * Here: delegates the list of actions to the DefaultColumnControlPopup.
643         * <p>
644         * PRE: columnVisibilityActions populated before calling this.
645         * 
646         */
647        protected void addVisibilityActionItems() {
648            getColumnControlPopup().addVisibilityActionItems(
649                    Collections.unmodifiableList(getColumnVisibilityActions()));
650        }
651    
652        /**
653         * Adds additional actions to the popup.
654         * Here: delegates the list of actions as returned by #getAdditionalActions() 
655         *   to the DefaultColumnControlPopup. 
656         * Does nothing if #getColumnActions() is empty.
657         * 
658         */
659        protected void addAdditionalActionItems() {
660            getColumnControlPopup().addAdditionalActionItems(
661                    Collections.unmodifiableList(getAdditionalActions()));
662        }
663    
664    
665        /**
666         * Creates and adds a ColumnVisiblityAction for every column that should be
667         * togglable via the column control. <p>
668         * 
669         * Here: all table columns contained in the <code>TableColumnModel</code> - 
670         * visible and invisible columns - to <code>createColumnVisibilityAction</code> and
671         * adds all not <code>null</code> return values.
672         * 
673         * <p>
674         * PRE: canControl()
675         * 
676         * @see #createColumnVisibilityAction
677         */
678        protected void createVisibilityActions() {
679            List<TableColumn> columns = table.getColumns(true);
680            for (TableColumn column : columns) {
681                ColumnVisibilityAction action = createColumnVisibilityAction(column);
682                if (action != null) {
683                    getColumnVisibilityActions().add(action);
684                }
685            }
686    
687        }
688    
689        /**
690         * Creates and returns a <code>ColumnVisibilityAction</code> for the given 
691         * <code>TableColumn</code>. The return value might be null, f.i. if the
692         * column should not be allowed to be toggled.
693         * 
694         * @param column the <code>TableColumn</code> to use for the action
695         * @return a ColumnVisibilityAction to use for the given column, 
696         *    may be <code>null</code>.
697         */
698        protected ColumnVisibilityAction createColumnVisibilityAction(TableColumn column) {
699            return new ColumnVisibilityAction(column);
700        }
701    
702        /**
703         * Lazyly creates and returns the List of visibility actions.
704         * 
705         * @return the list of visibility actions, guaranteed to be != null.
706         */
707        protected List<ColumnVisibilityAction> getColumnVisibilityActions() {
708            if (columnVisibilityActions == null) {
709                columnVisibilityActions = new ArrayList<ColumnVisibilityAction>();
710            }
711            return columnVisibilityActions;
712        }
713    
714    
715        /**
716         * creates and returns a list of additional Actions to add to the popup.
717         * Here: the actions are looked up in the table's actionMap according
718         * to the keys as returned from #getColumnControlActionKeys();
719         * 
720         * @return a list containing all additional actions to include into the popup.
721         */
722        protected List<Action> getAdditionalActions() {
723            List actionKeys = getColumnControlActionKeys();
724            List<Action> actions = new ArrayList<Action>();
725            for (Object key : actionKeys) {
726              actions.add(table.getActionMap().get(key));
727            }
728            return actions;
729        }
730    
731        /**
732         * Looks up and returns action keys to access actions in the 
733         * table's actionMap which should be included into the popup.
734         * 
735         * Here: all keys with isColumnControlActionKey(key). The list 
736         * is sorted by those keys.
737         * 
738         * @return the action keys of table's actionMap entries whose
739         *   action should be included into the popup.
740         */
741        @SuppressWarnings("unchecked")
742        protected List getColumnControlActionKeys() {
743            Object[] allKeys = table.getActionMap().allKeys();
744            List columnKeys = new ArrayList();
745            for (int i = 0; i < allKeys.length; i++) {
746                if (isColumnControlActionKey(allKeys[i])) {
747                    columnKeys.add(allKeys[i]);
748                }
749            }
750            // JW: this will blow for non-String keys!
751            // so this method is less decoupled from the
752            // decision method isControl than expected. 
753            Collections.sort(columnKeys);
754            return columnKeys;
755        }
756    
757        /**
758         * Here: true if a String key starts with #COLUMN_CONTROL_MARKER.
759         * 
760         * @param actionKey a key in the table's actionMap.
761         * @return a boolean to indicate whether the given actionKey maps to
762         *    an action which should be included into the popup.
763         *    
764         */
765        protected boolean isColumnControlActionKey(Object actionKey) {
766            return (actionKey instanceof String) && 
767                ((String) actionKey).startsWith(COLUMN_CONTROL_MARKER);
768        }
769    
770    
771        //--------------------------- init
772    
773        private void installTable(JXTable table) {
774            this.table = table;
775            table.addPropertyChangeListener(getTablePropertyChangeListener());
776            updateFromColumnModelChange(null);
777            updateFromTableEnabledChanged();
778        }
779    
780    
781        /**
782         * Initialize the column control button's gui
783         */
784        private void init() {
785            setFocusPainted(false);
786            setFocusable(false);
787            // this is a trick to get hold of the client prop which
788            // prevents closing of the popup
789            JComboBox box = new JComboBox();
790            Object preventHide = box.getClientProperty("doNotCancelPopup");
791            putClientProperty("doNotCancelPopup", preventHide);
792        }
793    
794    
795        /** 
796         * Creates and returns the default action for this button.
797         * @param icon 
798         * 
799         * @param icon the Icon to use in the action.
800         * @return the default action.
801         */
802        private Action createControlAction(Icon icon) {
803            
804            Action control = new AbstractAction() {
805    
806                public void actionPerformed(ActionEvent e) {
807                    togglePopup();
808                }
809    
810            };
811            control.putValue(Action.SMALL_ICON, icon);
812            return control;
813        }
814        
815        // -------------------------------- listeners
816    
817        /**
818         * Returns the listener to table's property changes. The listener is 
819         * lazily created if necessary. 
820         * @return the <code>PropertyChangeListener</code> for use with the 
821         *  table, guaranteed to be not <code>null</code>.
822         */
823        protected PropertyChangeListener getTablePropertyChangeListener() {
824            if (tablePropertyChangeListener == null) {
825                tablePropertyChangeListener = createTablePropertyChangeListener();
826            }
827            return tablePropertyChangeListener;
828        }
829    
830        /**
831         * Creates the listener to table's property changes. Subclasses are free
832         * to roll their own. <p>
833         * Implementation note: this listener reacts to table's <code>enabled</code> and
834         * <code>columnModel</code> properties and calls the respective 
835         * <code>updateFromXX</code> methodes.
836         * 
837         * @return the <code>PropertyChangeListener</code> for use with the table.
838         */
839        protected PropertyChangeListener createTablePropertyChangeListener() {
840            return new PropertyChangeListener() {
841                public void propertyChange(PropertyChangeEvent evt) {
842                    if ("columnModel".equals(evt.getPropertyName())) {
843                        updateFromColumnModelChange((TableColumnModel) evt
844                                .getOldValue());
845                    } else if ("enabled".equals(evt.getPropertyName())) {
846                        updateFromTableEnabledChanged();
847                    }
848                }
849            };
850        }
851    
852        /**
853         * Returns the listener to table's column model. The listener is 
854         * lazily created if necessary. 
855         * @return the <code>TableColumnModelListener</code> for use with the 
856         *  table's column model, guaranteed to be not <code>null</code>.
857         */
858        protected TableColumnModelListener getColumnModelListener() {
859            if (columnModelListener == null) {
860                columnModelListener = createColumnModelListener();
861            }
862            return columnModelListener;
863        }
864        
865        /**
866         * Creates the listener to columnModel. Subclasses are free to roll their
867         * own.
868         * <p>
869         * Implementation note: this listener reacts to "real" columnRemoved/-Added by
870         * populating the popups content from scratch.
871         * 
872         * @return the <code>TableColumnModelListener</code> for use with the
873         *         table's columnModel.
874         */
875        protected TableColumnModelListener createColumnModelListener() {
876            return new TableColumnModelListener() {
877                /** Tells listeners that a column was added to the model. */
878                public void columnAdded(TableColumnModelEvent e) {
879                    // quickfix for #192
880                    if (!isVisibilityChange(e, true)) {
881                        populatePopup();
882                    }
883                }
884    
885                /** Tells listeners that a column was removed from the model. */
886                public void columnRemoved(TableColumnModelEvent e) {
887                    if (!isVisibilityChange(e, false)) {
888                        populatePopup();
889                    }
890                }
891    
892                /**
893                 * Check if the add/remove event is triggered by a move to/from the
894                 * invisible columns. 
895                 * 
896                 * PRE: the event must be received in columnAdded/Removed.
897                 * 
898                 * @param e the received event
899                 * @param added if true the event is assumed to be received via
900                 *        columnAdded, otherwise via columnRemoved.
901                 * @return boolean indicating whether the removed/added is a side-effect
902                 *    of hiding/showing the column.
903                 */
904                private boolean isVisibilityChange(TableColumnModelEvent e,
905                        boolean added) {
906                    // can't tell
907                    if (!(e.getSource() instanceof DefaultTableColumnModelExt))
908                        return false;
909                    DefaultTableColumnModelExt model = (DefaultTableColumnModelExt) e
910                            .getSource();
911                    if (added) {
912                        return model.isAddedFromInvisibleEvent(e.getToIndex());
913                    } else {
914                        return model.isRemovedToInvisibleEvent(e.getFromIndex());
915                    }
916                }
917    
918                /** Tells listeners that a column was repositioned. */
919                public void columnMoved(TableColumnModelEvent e) {
920                }
921    
922                /** Tells listeners that a column was moved due to a margin change. */
923                public void columnMarginChanged(ChangeEvent e) {
924                }
925    
926                /**
927                 * Tells listeners that the selection model of the TableColumnModel
928                 * changed.
929                 */
930                public void columnSelectionChanged(ListSelectionEvent e) {
931                }
932            };
933        }
934    
935    
936    
937    } // end class ColumnControlButton