001: /*
002: * $Id: DefaultSelectionMapper.java,v 1.2 2006/10/23 12:08:45 kleopatra Exp $
003: *
004: * Copyright 2006 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.decorator;
023:
024: import javax.swing.*;
025: import javax.swing.event.ListSelectionListener;
026: import javax.swing.event.ListSelectionEvent;
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 DefaultSelectionMapper implements SelectionMapper {
037:
038: /** selection in view coordinates. */
039: private ListSelectionModel viewSelection;
040:
041: /** selection in model coordinates. */
042: protected final DefaultListSelectionModel modelSelection = new DefaultListSelectionModel();
043:
044: /** mapping pipeline. */
045: private FilterPipeline pipeline;
046:
047: /** listener to view selection. */
048: private ListSelectionListener viewSelectionListener;
049:
050: /**
051: * Whether selection mapping is enabled. If true, we're currently
052: * observing the view selection, using that to keep the model selection
053: * up-to-date.
054: */
055: private boolean enabled = false;
056:
057: /** listener to mapping pipeline. */
058: private PipelineListener pipelineListener;
059:
060: /**
061: * PRE: selection != null;
062: *
063: * @param pipeline
064: * @param selection
065: */
066: public DefaultSelectionMapper(FilterPipeline pipeline,
067: ListSelectionModel selection) {
068: setViewSelectionModel(selection);
069: setEnabled(true);
070: setFilters(pipeline);
071: }
072:
073: /** {@inheritDoc} */
074: public void setViewSelectionModel(
075: ListSelectionModel viewSelectionModel) {
076: if (viewSelectionModel == null)
077: throw new IllegalArgumentException();
078:
079: boolean wasEnabled = isEnabled();
080: setEnabled(false);
081: try {
082: clearModelSelection();
083: this .viewSelection = viewSelectionModel;
084: mapTowardsModel();
085: } finally {
086: setEnabled(wasEnabled);
087: }
088: }
089:
090: /** {@inheritDoc} */
091: public ListSelectionModel getViewSelectionModel() {
092: return viewSelection;
093: }
094:
095: public void setFilters(FilterPipeline pipeline) {
096: FilterPipeline old = this .pipeline;
097: if (old != null) {
098: old.removePipelineListener(pipelineListener);
099: }
100: this .pipeline = pipeline;
101: if (pipeline != null) {
102: pipeline.addPipelineListener(getPipelineListener());
103: }
104: mapTowardsView();
105: }
106:
107: /**
108: * Populate view selection from model selection. This is used to keep the
109: * view's logical selection in sync whenever the model changes due to
110: * filtering or sorting.
111: */
112: protected void mapTowardsView() {
113: if (!enabled)
114: return;
115:
116: setEnabled(false);
117: try {
118: clearViewSelection();
119:
120: int[] selected = getSelectedRows(modelSelection);
121: for (int i = 0; i < selected.length; i++) {
122: int index = convertToView(selected[i]);
123: // index might be -1, but then addSelectionInterval ignores it.
124: viewSelection.addSelectionInterval(index, index);
125: }
126: int lead = modelSelection.getLeadSelectionIndex();
127: // TODO: PENDING: JW - this is a quick hack for spurious AIOB - need to enquire why
128: // they happen in the first place
129: if (lead >= 0) {
130: lead = convertToView(lead);
131: }
132: if (viewSelection instanceof DefaultListSelectionModel) {
133: ((DefaultListSelectionModel) viewSelection)
134: .moveLeadSelectionIndex(lead);
135: } else {
136: // PENDING: not tested, don't have a non-DefaultXX handy
137: viewSelection.removeSelectionInterval(lead, lead);
138: viewSelection.addSelectionInterval(lead, lead);
139: }
140: } finally {
141: setEnabled(true);
142: }
143: }
144:
145: /** {@inheritDoc} */
146: public void setEnabled(boolean enabled) {
147: if (enabled == this .enabled)
148: return;
149: this .enabled = enabled;
150:
151: if (enabled) {
152: viewSelection.setValueIsAdjusting(false);
153: viewSelection
154: .addListSelectionListener(getViewSelectionListener());
155: } else {
156: viewSelection
157: .removeListSelectionListener(viewSelectionListener);
158: viewSelection.setValueIsAdjusting(true);
159: }
160: }
161:
162: /** {@inheritDoc} */
163: public boolean isEnabled() {
164: return enabled;
165: }
166:
167: public void clearModelSelection() {
168: if (modelSelection == null)
169: return;
170: // TODO: JW: need to reset anchor/lead?
171: modelSelection.clearSelection();
172: modelSelection.setAnchorSelectionIndex(-1);
173: modelSelection.setLeadSelectionIndex(-1);
174: }
175:
176: /**
177: *
178: */
179: private void clearViewSelection() {
180: // TODO: JW - hmm... clearSelection doesn't reset the lead/anchor. Why not?
181: viewSelection.clearSelection();
182: viewSelection.setAnchorSelectionIndex(-1);
183: viewSelection.setLeadSelectionIndex(-1);
184: }
185:
186: public void insertIndexInterval(int start, int length,
187: boolean before) {
188: modelSelection.insertIndexInterval(start, length, before);
189: }
190:
191: public void removeIndexInterval(int start, int end) {
192: modelSelection.removeIndexInterval(start, end);
193: }
194:
195: /**
196: * Populate view selection from model selection.
197: */
198: private void mapTowardsModel() {
199: if (modelSelection == null)
200: return;
201:
202: clearModelSelection();
203: int[] selected = getSelectedRows(viewSelection);
204: for (int i = 0; i < selected.length; i++) {
205: int modelIndex = convertToModel(selected[i]);
206: modelSelection.addSelectionInterval(modelIndex, modelIndex);
207: }
208: if (selected.length > 0) {
209: // convert lead selection index to model coordinates
210: modelSelection
211: .moveLeadSelectionIndex(convertToModel(viewSelection
212: .getLeadSelectionIndex()));
213: }
214: }
215:
216: private int convertToModel(int index) {
217: // TODO: JW: check for valid index? must be < pipeline.getOutputSize()
218: return (pipeline != null) && pipeline.isAssigned() ? pipeline
219: .convertRowIndexToModel(index) : index;
220: }
221:
222: private int convertToView(int index) {
223: // TODO: JW: check for valid index? must be < pipeline.getInputSize()
224: return (pipeline != null) && pipeline.isAssigned() ? pipeline
225: .convertRowIndexToView(index) : index;
226: }
227:
228: /**
229: * Respond to a change in the view selection by updating the view selection.
230: *
231: * @param firstIndex the first view index that changed, inclusive
232: * @param lastIndex the last view index that changed, inclusive
233: */
234: private void mapTowardsModel(int firstIndex, int lastIndex) {
235: int safeFirstIndex = Math.max(0, firstIndex);
236: for (int i = safeFirstIndex; i <= lastIndex; i++) {
237: int modelIndex = convertToModel(i);
238: if (viewSelection.isSelectedIndex(i)) {
239: modelSelection.addSelectionInterval(modelIndex,
240: modelIndex);
241: } else {
242: modelSelection.removeSelectionInterval(modelIndex,
243: modelIndex);
244: }
245: }
246: int lead = viewSelection.getLeadSelectionIndex();
247: if (lead >= 0) {
248: modelSelection.moveLeadSelectionIndex(convertToModel(lead));
249: }
250:
251: }
252:
253: private int[] getSelectedRows(ListSelectionModel selection) {
254: int iMin = selection.getMinSelectionIndex();
255: int iMax = selection.getMaxSelectionIndex();
256:
257: if ((iMin == -1) || (iMax == -1)) {
258: return new int[0];
259: }
260:
261: int[] rvTmp = new int[1 + (iMax - iMin)];
262: int n = 0;
263: for (int i = iMin; i <= iMax; i++) {
264: if (selection.isSelectedIndex(i)) {
265: rvTmp[n++] = i;
266: }
267: }
268: int[] rv = new int[n];
269: System.arraycopy(rvTmp, 0, rv, 0, n);
270: return rv;
271: }
272:
273: /**
274: * When the filter pipeline changes, update our view selection.
275: */
276: private PipelineListener getPipelineListener() {
277: if (pipelineListener == null) {
278: pipelineListener = new PipelineListener() {
279:
280: public void contentsChanged(PipelineEvent e) {
281: mapTowardsView();
282: }
283:
284: };
285: }
286: return pipelineListener;
287: }
288:
289: /**
290: * When the view selection changes, update our model selection.
291: */
292: private ListSelectionListener getViewSelectionListener() {
293: if (viewSelectionListener == null) {
294: viewSelectionListener = new ListSelectionListener() {
295:
296: public void valueChanged(ListSelectionEvent e) {
297: if (e.getValueIsAdjusting())
298: return;
299: mapTowardsModel(e.getFirstIndex(), e.getLastIndex());
300: }
301:
302: };
303: }
304: return viewSelectionListener;
305: }
306:
307: }
|