001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.swing.tabcontrol.plaf;
043:
044: import java.util.Iterator;
045: import java.util.Set;
046: import javax.swing.SingleSelectionModel;
047: import javax.swing.event.ChangeListener;
048: import javax.swing.event.ListDataEvent;
049: import org.netbeans.swing.tabcontrol.TabDataModel;
050: import org.netbeans.swing.tabcontrol.event.ArrayDiff;
051: import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent;
052: import org.netbeans.swing.tabcontrol.event.ComplexListDataListener;
053: import org.netbeans.swing.tabcontrol.event.VeryComplexListDataEvent;
054: import org.openide.util.ChangeSupport;
055:
056: /**
057: * Default implementation of tab selection model. Listens to the supplied data
058: * model and updates the selection appropriately on all add/remove events so that
059: * the actual selection does not change if items are inserted into the model ahead
060: * of the current selection, etc.
061: *
062: * @author Tim Boudreau
063: */
064: final class DefaultTabSelectionModel implements SingleSelectionModel,
065: ComplexListDataListener {
066: TabDataModel dataModel;
067: int sel = -1;
068: private final ChangeSupport cs = new ChangeSupport(this );
069:
070: /**
071: * Creates a new instance of DefaultTabSelectionModel
072: */
073: public DefaultTabSelectionModel(TabDataModel tdm) {
074: dataModel = tdm;
075: attach();
076: }
077:
078: public void attach() {
079: dataModel.addComplexListDataListener(this );
080: }
081:
082: public void detach() {
083: dataModel.removeComplexListDataListener(this );
084: }
085:
086: public void clearSelection() {
087: sel = -1;
088: cs.fireChange();
089: }
090:
091: public int getSelectedIndex() {
092: return sel;
093: }
094:
095: public boolean isSelected() {
096: return sel != -1;
097: }
098:
099: public void setSelectedIndex(int index) {
100: if (index != sel) {
101: int oldIndex = sel;
102: if ((index < -1) || (index >= dataModel.size())) {
103: throw new IllegalArgumentException(
104: "Selected index set to " + index
105: + " but model size is only "
106: + dataModel.size());
107: }
108: sel = index;
109: cs.fireChange();
110: }
111: }
112:
113: private void adjustSelectionForEvent(ListDataEvent e) {
114: if (e.getType() == e.CONTENTS_CHANGED || sel == -1) {
115: return;
116: }
117: int start = e.getIndex0();
118: int end = e.getIndex1() + 1;
119: if (e.getType() == e.INTERVAL_REMOVED) {
120: if (sel < start) {
121: return;
122: } else {
123: if (sel >= start) {
124: if (sel > end) {
125: sel -= end - start;
126: } else {
127: sel = start;
128: if (sel >= dataModel.size()) {
129: sel = dataModel.size() - 1;
130: }
131: }
132: cs.fireChange();
133: }
134: }
135: } else {
136: if (sel < start) {
137: //not affected, do nothing
138: return;
139: }
140: if (sel >= start) {
141: if (end - 1 == start) {
142: sel++;
143: } else if (sel < end) {
144: sel = (end + (sel - start)) - 1;
145: } else {
146: sel += (end - start) - 1;
147: }
148: cs.fireChange();
149: }
150: }
151: }
152:
153: public void contentsChanged(ListDataEvent e) {
154: adjustSelectionForEvent(e);
155: }
156:
157: public void intervalAdded(ListDataEvent e) {
158: adjustSelectionForEvent(e);
159: }
160:
161: public void intervalRemoved(ListDataEvent e) {
162: adjustSelectionForEvent(e);
163: }
164:
165: public void indicesAdded(ComplexListDataEvent e) {
166: if (sel < 0)
167: return;
168: int[] indices = e.getIndices();
169: java.util.Arrays.sort(indices);
170: int offset = 0;
171: for (int i = 0; i < indices.length; i++) {
172: if (sel >= indices[i]) {
173: offset++;
174: } else {
175: break;
176: }
177: }
178: if (offset > 0) {
179: sel += offset;
180: cs.fireChange();
181: }
182: }
183:
184: public void indicesRemoved(ComplexListDataEvent e) {
185: if (sel < 0)
186: return;
187: int[] indices = e.getIndices();
188: java.util.Arrays.sort(indices);
189: int offset = -1;
190: for (int i = 0; i < indices.length; i++) {
191: if (sel > indices[i]) {
192: offset--;
193: } else {
194: break;
195: }
196: }
197: if (sel == dataModel.size()) {
198: sel -= 1;
199: cs.fireChange();
200: return;
201: }
202: if (dataModel.size() == 0) {
203: sel = -1;
204: cs.fireChange();
205: } else if (offset != 0) {
206: sel = Math.max(-1, Math.min(sel + offset, -1));
207: cs.fireChange();
208: }
209: }
210:
211: public void indicesChanged(ComplexListDataEvent e) {
212: if (sel < 0)
213: return;
214: if (e instanceof VeryComplexListDataEvent) { //it always will be
215:
216: ArrayDiff dif = ((VeryComplexListDataEvent) e).getDiff();
217:
218: boolean changed = false;
219:
220: if (dif == null) {
221: //no differences
222: return;
223: }
224:
225: //Get the deleted and added indices
226: Set<Integer> deleted = dif.getDeletedIndices();
227: Set<Integer> added = dif.getAddedIndices();
228:
229: //create an Integer to compare
230: Integer idx = new Integer(getSelectedIndex());
231:
232: //Don't iterate if everything was closed, we know what to do
233: if (dataModel.size() == 0) {
234: sel = -1;
235: cs.fireChange();
236: return;
237: }
238:
239: //Iterate all of the deleted items, and count how many were
240: //removed at indices lower than the selection, so we can subtract
241: //that from the selected index to keep selection on the same tab
242: Iterator<Integer> i = deleted.iterator();
243: int offset = 0;
244: Integer curr;
245: while (i.hasNext()) {
246: curr = i.next();
247: if (curr.compareTo(idx) <= 0) {
248: offset++;
249: }
250: }
251:
252: //Iterate all of the added items, and count how many were added at
253: //indices below the selected index, so we can add that to the selected
254: //index
255: i = added.iterator();
256: while (i.hasNext()) {
257: curr = i.next();
258: if (curr.compareTo(idx) >= 0) {
259: offset--;
260: }
261: }
262:
263: sel -= offset;
264: if (sel < 0) {
265: //The tab at index 0 was closed, but we always want to show
266: //something if we can, so change it to 0 if possible
267: sel = dataModel.size() > 0 ? 0 : -1;
268: }
269:
270: //Make sure we're not off the end of the array - we could be if the
271: //selection was the last and it and others were removed
272: if (sel >= dataModel.size()) {
273: sel = dataModel.size() - 1;
274: }
275:
276: if (offset != 0) {
277: cs.fireChange();
278: }
279: }
280: //do nothing
281: }
282:
283: public void addChangeListener(ChangeListener listener) {
284: cs.addChangeListener(listener);
285: }
286:
287: public synchronized void removeChangeListener(
288: ChangeListener listener) {
289: cs.removeChangeListener(listener);
290: }
291:
292: }
|