001    /*
002     * $Id: SelectionMapper.java,v 1.4 2006/05/14 15:55:53 dmouse Exp $
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    package org.jdesktop.swingx.decorator;
022    
023    import javax.swing.DefaultListSelectionModel;
024    import javax.swing.ListSelectionModel;
025    import javax.swing.event.ListSelectionEvent;
026    import javax.swing.event.ListSelectionListener;
027    
028    /**
029     * Responsible for keeping track of selection in model coordinates.<p>
030     * 
031     * updates view selection on pipeline change.
032     * updates model selection on view selection change.
033     * 
034     * @author Jeanette Winzenburg
035     */
036    public class SelectionMapper {
037    
038        /** selection in view coordinates. */
039        private ListSelectionModel viewSelection;
040        
041        /** selection in model coordinates. */
042        protected DefaultListSelectionModel modelSelection;
043    
044        /** mapping pipeline. */
045        private FilterPipeline pipeline;
046    
047        /** listener to view selection. */
048        private ListSelectionListener viewSelectionListener;
049        /** state flag for locking non-listening phases. */
050        private boolean isListening;
051    
052        /** listener to mapping pipeline. */
053        private PipelineListener pipelineListener;
054    
055    
056        /**
057         * PRE: selection != null;
058         * 
059         * @param pipeline
060         * @param selection
061         */
062        public SelectionMapper(FilterPipeline pipeline, ListSelectionModel selection) {
063            modelSelection = new DefaultListSelectionModel();
064            setViewSelectionModel(selection);
065            setFilters(pipeline);
066        }
067    
068        /**
069         * sets the view selection model. Must not be null.
070         * 
071         * @param selection holding selected indices in view coordinates
072         */
073        public void setViewSelectionModel(ListSelectionModel selection) {
074            ListSelectionModel old = this.viewSelection;
075            if (old != null) {
076                old.removeListSelectionListener(viewSelectionListener);
077    //            modelSelection.clearSelection();
078                clearModelSelection();
079            }
080            this.viewSelection = selection;
081            mapTowardsModel();
082            viewSelection.addListSelectionListener(getViewSelectionListener());
083            isListening = true;
084        }
085    
086        /**
087         * TODO: temporarily added for testing...
088         * @return view selection model
089         */
090        public ListSelectionModel getViewSelectionModel() {
091            return viewSelection;
092        }
093        
094        public void setFilters(FilterPipeline pipeline) {
095            FilterPipeline old = this.pipeline;
096            if (old != null) {
097                old.removePipelineListener(pipelineListener);
098            }
099            this.pipeline = pipeline;
100            if (pipeline != null) {
101                pipeline.addPipelineListener(getPipelineListener());
102            }
103            restoreSelection();
104        }
105    
106    
107        public void restoreSelection() {
108            lock();
109            clearViewSelection();
110    
111            int[] selected = getSelectedRows(modelSelection);
112            for (int i = 0; i < selected.length; i++) {
113              int index = convertToView(selected[i]);
114              // index might be -1, but then addSelectionInterval ignores it. 
115              viewSelection.addSelectionInterval(index, index);
116            }
117            int lead = modelSelection.getLeadSelectionIndex();
118            // TODO: PENDING: JW - this is a quick hack for spurious AIOB - need to enquire why
119            // they happen in the first place
120            if (lead >= 0) {
121                lead = convertToView(lead);
122            }
123            if (viewSelection instanceof DefaultListSelectionModel) {
124                ((DefaultListSelectionModel) viewSelection).moveLeadSelectionIndex(lead);
125            } else {
126                // PENDING: not tested, don't have a non-DefaultXX handy
127                viewSelection.removeSelectionInterval(lead, lead);
128                viewSelection.addSelectionInterval(lead, lead);
129            }
130            unlock();
131        }
132    
133    
134        public void unlock() {
135            if (!isListening) {
136                viewSelection.setValueIsAdjusting(false);
137                viewSelection.addListSelectionListener(getViewSelectionListener());
138                isListening = true;
139            }
140        }
141    
142        public void lock() {
143            if (isListening) {
144                viewSelection.removeListSelectionListener(viewSelectionListener);
145                viewSelection.setValueIsAdjusting(true);
146                isListening = false;
147            }
148        }
149    
150        public void clearModelSelection() {
151            // TODO: JW: need to reset anchor/lead?
152            modelSelection.clearSelection();
153            modelSelection.setAnchorSelectionIndex(-1);
154            modelSelection.setLeadSelectionIndex(-1);
155        }
156    
157        /**
158         * 
159         */
160        private void clearViewSelection() {
161            // TODO: JW - hmm... clearSelection doesn't reset the lead/anchor. Why not?
162            viewSelection.clearSelection();
163            viewSelection.setAnchorSelectionIndex(-1);
164            viewSelection.setLeadSelectionIndex(-1);
165        }
166    
167        public void insertIndexInterval(int start, int length, boolean before) {
168            modelSelection.insertIndexInterval(start, length, before);
169        }
170    
171        public void removeIndexInterval(int start, int end) {
172            modelSelection.removeIndexInterval(start, end);
173        }
174    
175        private void mapTowardsModel() {
176    //        modelSelection.clearSelection();
177            clearModelSelection();
178            int[] selected = getSelectedRows(viewSelection); 
179            for (int i = 0; i < selected.length; i++) {
180                int modelIndex = convertToModel(selected[i]);
181                modelSelection.addSelectionInterval(modelIndex, modelIndex); 
182            }
183            if (selected.length > 0) {
184                // convert lead selection index to model coordinates
185                modelSelection.moveLeadSelectionIndex(convertToModel(viewSelection.getLeadSelectionIndex()));
186            }
187        }
188    
189        private int convertToModel(int index) {
190            // TODO: JW: check for valid index? must be < pipeline.getOutputSize()
191            return (pipeline != null) && pipeline.isAssigned() ? pipeline.convertRowIndexToModel(index) : index;
192        }
193        
194        private int convertToView(int index) {
195            // TODO: JW: check for valid index? must be < pipeline.getInputSize()
196            return (pipeline != null) && pipeline.isAssigned() ? pipeline.convertRowIndexToView(index) : index;
197        }
198        
199        protected void updateFromViewSelectionChanged(int firstIndex, int lastIndex) {
200            for (int i = firstIndex; i <= lastIndex; i++) {
201                int modelIndex = convertToModel(i);
202                if (viewSelection.isSelectedIndex(i)) {
203                    modelSelection.addSelectionInterval(modelIndex, modelIndex);
204                } else {
205                    modelSelection.removeSelectionInterval(modelIndex, modelIndex);
206                }
207            }
208            int lead = viewSelection.getLeadSelectionIndex();
209            if (lead >= 0) {
210                modelSelection.moveLeadSelectionIndex(convertToModel(lead));
211            }
212    
213        }
214    
215        protected void updateFromPipelineChanged() {
216            restoreSelection();
217        }
218    
219        private int[] getSelectedRows(ListSelectionModel selection) {
220            int iMin = selection.getMinSelectionIndex();
221            int iMax = selection.getMaxSelectionIndex();
222    
223            if ((iMin == -1) || (iMax == -1)) {
224                return new int[0];
225            }
226    
227            int[] rvTmp = new int[1 + (iMax - iMin)];
228            int n = 0;
229            for (int i = iMin; i <= iMax; i++) {
230                if (selection.isSelectedIndex(i)) {
231                    rvTmp[n++] = i;
232                }
233            }
234            int[] rv = new int[n];
235            System.arraycopy(rvTmp, 0, rv, 0, n);
236            return rv;
237        }
238        
239        private PipelineListener getPipelineListener() {
240            if (pipelineListener == null) {
241                pipelineListener = new PipelineListener() {
242    
243                    public void contentsChanged(PipelineEvent e) {
244                        updateFromPipelineChanged();
245                    }
246                    
247                };
248            }
249            return pipelineListener;
250        }
251    
252        private ListSelectionListener getViewSelectionListener() {
253            if (viewSelectionListener == null) {
254                viewSelectionListener = new ListSelectionListener() {
255    
256                    public void valueChanged(ListSelectionEvent e) {
257                        if (e.getValueIsAdjusting()) return;
258                        updateFromViewSelectionChanged(e.getFirstIndex(), e.getLastIndex());
259                    }
260                    
261                };
262            }
263            return viewSelectionListener;
264        }
265    
266    }