001: /*
002: ItsNat Java Web Application Framework
003: Copyright (C) 2007 Innowhere Software Services S.L., Spanish Company
004: Author: Jose Maria Arranz Santamaria
005:
006: This program is free software: you can redistribute it and/or modify
007: it under the terms of the GNU Affero General Public License as published by
008: the Free Software Foundation, either version 3 of the License, or
009: (at your option) any later version. See the GNU Affero General Public
010: License for more details. See the copy of the GNU Affero General Public License
011: included in this program. If not, see <http://www.gnu.org/licenses/>.
012: */
013:
014: package org.itsnat.impl.comp;
015:
016: import javax.swing.ListSelectionModel;
017: import javax.swing.event.ListSelectionListener;
018:
019: /**
020: *
021: * @author jmarranz
022: */
023: public class ListSelectionModelMgrImpl {
024: protected ListSelectionModel selectionModel;
025: protected int size = 0;
026:
027: /** Creates a new instance of ListSelectionModelMgrImpl */
028: public ListSelectionModelMgrImpl(ListSelectionModel selectionModel) {
029: if (selectionModel == null)
030: selectionModel = EmptyListSelectionModelImpl.SINGLETON;
031:
032: this .selectionModel = selectionModel;
033:
034: //syncWithDataModel();
035: }
036:
037: public static ListSelectionModelMgrImpl newListSelectionModelMgr(
038: ListSelectionModel selectionModel, int size) {
039: ListSelectionModelMgrImpl selModelMgr = new ListSelectionModelMgrImpl(
040: selectionModel);
041:
042: selModelMgr.syncWithDataModel(size);
043:
044: return selModelMgr;
045: }
046:
047: public void dispose() {
048: removeAllUpdateModel();
049: }
050:
051: public int getSize() {
052: return size;
053: }
054:
055: public void setSize(int size) {
056: int oldSize = this .size;
057: int diff = size - oldSize;
058: if (diff > 0) // Han sido añadidas al final
059: {
060: insertElementUpdateModel(oldSize, diff);
061: } else if (diff < 0) // Han sido eliminadas del final
062: {
063: diff = -diff; // lo ponemos como positivo
064: removeRangeUpdateModel(oldSize - diff, oldSize - 1);
065: }
066: }
067:
068: public ListSelectionModel getListSelectionModel() {
069: return selectionModel;
070: }
071:
072: public void syncWithDataModel(int size) {
073: if (this .size > 0) // Se ha cambiado el data model
074: removeAllUpdateModel();
075:
076: this .size = 0;
077:
078: // Iniciando el selection model, el data model es el que manda
079: // porque si no hay datos nos da igual como esté definido el selectionModel
080: // dado como argumento.
081: // Es usado cuando se define el SelectionModel existiendo ya
082: // un data model que puede contener ya datos.
083:
084: insertElementUpdateModel(0, size);
085:
086: // Por si acaso hubiera elementos seleccionados en el modelo usado por el usuario
087: ListSelectionModel selectionModel = getListSelectionModel();
088: selectionModel.clearSelection();
089: }
090:
091: public void insertElementUpdateModel(int index) {
092: insertElementUpdateModel(index, 1);
093: }
094:
095: public void insertElementUpdateModel(int index, int length) {
096: if (length == 0)
097: return;
098:
099: this .size += length;
100:
101: ListSelectionModel selectionModel = getListSelectionModel();
102:
103: /*
104: boolean oldAdjusting = selectionModel.getValueIsAdjusting();
105: selectionModel.setValueIsAdjusting(true); // Evita procesar muchos eventos para procesar al final todos en uno
106: try
107: {
108: */
109: selectionModel.insertIndexInterval(index, length, true);
110: // Por defecto pone los nuevos índices seleccionados en ciertos casos
111: // (cuando hay selección múltiple) el caso es que no genera evento o si lo genera no incluye los índices de los nuevos elementos seleccionados
112:
113: // Se ha detectado el extraño caso de selection model vacío (anteriormente con algo)
114: // pero que al añadir un primer elemento (index = 0, length = 0) el caso es que genera
115: // un evento con índices 0 y 1 existiendo un único elemento en teoría (size es 1).
116: /*
117: if (selectionModel.isSelectedIndex(index))
118: {
119: // Fueron seleccionados, los quitamos, no queremos que el nuevo elemento añadido esté seleccionado:
120: selectionModel.removeSelectionInterval(index,index + length - 1);
121: }
122: */
123: /*
124: }
125: finally
126: {
127: selectionModel.setValueIsAdjusting(false); // Envía un evento con todos los cambios
128: selectionModel.setValueIsAdjusting(oldAdjusting); // Restaura
129: }
130: */
131: }
132:
133: public void removeRangeUpdateModel(int fromIndex, int toIndex) {
134: getListSelectionModel().removeIndexInterval(fromIndex, toIndex);
135: this .size -= toIndex - fromIndex + 1;
136: }
137:
138: public void removeAllUpdateModel() {
139: int last = this .size - 1;
140: if (last >= 0)
141: removeRangeUpdateModel(0, last);
142: this .size = 0; // redundante pero para que quede claro
143: }
144:
145: public void changeSelectionModel(int index, boolean toggle,
146: boolean extend, boolean selected) {
147: ListSelectionModel sm = getListSelectionModel();
148:
149: // Es como está en JTable (1.4/1.5) y similar a http://developer.classpath.org/doc/javax/swing/JTable-source.html#line.4945
150: // pero el comportamiento también es válido para los List (con selección múltiple sobre todo)
151: if (extend && toggle) // shift+ctrl-click
152: {
153: sm.setAnchorSelectionIndex(index);
154: } else if (toggle) // ctrl-click
155: {
156: if (selected)
157: sm.removeSelectionInterval(index, index);
158: else
159: addSelectionIntervalWithContigous(index, index);
160: // sm.addSelectionInterval(index, index); Si fuera 1.5 valdría esta llamada pero en 1.4 no funciona bien en SINGLE_INTERVAL_SELECTION
161: } else if (extend) // shift-click
162: {
163: //sm.setLeadSelectionIndex(index);
164: sm
165: .setSelectionInterval(sm.getAnchorSelectionIndex(),
166: index); // Como en JTable 1.5
167: // Si index es menor que el anchor el propio selection model hace el cambio
168: } else // click (sin teclas)
169: {
170: sm.setSelectionInterval(index, index);
171: }
172: }
173:
174: public int[] getSelectedIndices() {
175: ListSelectionModel selModel = getListSelectionModel();
176:
177: int iMin = selModel.getMinSelectionIndex();
178: int iMax = selModel.getMaxSelectionIndex();
179:
180: if ((iMin < 0) || (iMax < 0))
181: return new int[0];
182:
183: int[] indices = new int[1 + (iMax - iMin)];
184: int n = 0;
185: for (int i = iMin; i <= iMax; i++) {
186: // No todos en el rango están seleccionados
187: if (selModel.isSelectedIndex(i)) {
188: indices[n] = i;
189: n++;
190: }
191: }
192: int[] indicesFinal = new int[n];
193: System.arraycopy(indices, 0, indicesFinal, 0, n);
194: return indicesFinal;
195: }
196:
197: public void addSelectionIntervalWithContigous(int first, int end) {
198: ListSelectionModel selModel = getListSelectionModel();
199: int mode = selModel.getSelectionMode();
200: if (mode == ListSelectionModel.SINGLE_INTERVAL_SELECTION) {
201: // El DefaultSelectionModel de la JVM 1.4 tiene un penoso error
202: // conceptual en addSelectionInterval en el caso SINGLE_INTERVAL_SELECTION,
203: // pues en este caso debería ver si hay seleccionados adjacentes para formar
204: // un intervalo más grande, pues no es así lo que hace es un setSelectionInterval,
205: // según la documentación de setSelectionMode(int) los métodos setSelectionInterval
206: // y addSelectionInterval son idénticos en este modo, esto se mantiene en la documentación
207: // de la 1.5 pero no es verdad, en la 1.5 forma un nuevo conjunto con los adjacentes.
208: // setSelectionInterval que viene a ser una substitución de seleccionados haya lo que haya más que un "añadir".
209: // En el caso JVM 1.5 funciona correctamente.
210: // Para hacer que funcione en ambos casos extendemos first y end
211: // a los contiguos por defecto y por exceso seleccionados.
212:
213: for (int i = first - 1; (i >= 0)
214: && selModel.isSelectedIndex(i); i--)
215: first = i;
216:
217: int max = selModel.getMaxSelectionIndex();
218: for (int i = end + 1; (i <= max)
219: && selModel.isSelectedIndex(i); i++)
220: end = i;
221: }
222:
223: selModel.addSelectionInterval(first, end);
224: }
225:
226: public void setSelectedIndices(int[] indices) {
227: // Este método es muy útil para procesar el evento "change" en el SELECT en el navegador
228: // El <select multiple="multiple"> permite múltiple selección
229: // pero es el ListSelectionModel el que en última instancia
230: // debe imponer cuales están seleccionados o no, por ello
231: // detectamos los cambios que han habido en el cliente y los
232: // notificamos al selection model que decida.
233:
234: // NO hacemos setServerUpdatingFromClient(true) porque necesitamos
235: // que el servidor propague al cliente los que verdaderamente han de quedar seleccionados
236: // que puede diferir de lo que hay en el cliente cuando se genera el evento
237:
238: // Ej. si el selection model está en modo selección única, al seleccionar
239: // otro en el cliente habrá dos seleccionados, detectaremos que el nuevo item seleccionado
240: // es el que ha cambiado su estado y notificaremos al selection model,
241: // el selection model decidirá que el previamente seleccionado debe de dejar de estarlo.
242:
243: // Todo esto también evita hacer un clearSelection()
244: // que mandaría un montón de código al cliente
245: // Si la selección no cambia no se generan eventos.
246:
247: boolean[] newState = new boolean[size];
248: for (int i = 0; i < indices.length; i++) {
249: int index = indices[i];
250: newState[index] = true;
251: }
252:
253: // Obtenemos la "foto" de los cambiados antes de hacer algún cambio en la selección
254: // pues el cambio en uno puede cambiar a otros segun el modo de selección
255: ListSelectionModel selModel = getListSelectionModel();
256: boolean[] changed = new boolean[size];
257: for (int i = 0; i < size; i++) {
258: boolean selected = newState[i];
259: changed[i] = (selected != selModel.isSelectedIndex(i));
260: }
261:
262: boolean oldAdjusting = selModel.getValueIsAdjusting();
263: selModel.setValueIsAdjusting(true); // Evita procesar muchos eventos para procesar al final todos en uno
264:
265: boolean isSelectionInterval = false;
266: int first = -1;
267: int end = -1;
268: try {
269: // Agrupamos los cambios en intervalos pues en el modo de selección
270: // de un sólo intervalo necesitamos pasarlo en una sóla llamada
271: // pues si pasamos de uno en uno el selecion model detectará huecos que pueden no existir quitando
272: // un intervalo existente que podría verdaderamente estar seguido del nuevo
273: for (int i = 0; i < size; i++) {
274: if (changed[i]) // Ha cambiado
275: {
276: boolean selected = newState[i];
277: if (selected) {
278: if (first >= 0) // hay un intervalo ya abierto
279: {
280: if (isSelectionInterval) {
281: end = i; // Uno más al intervalo continuo
282: } else {
283: // Cerramos el intervalo de no seleccionados primero
284: selModel.removeSelectionInterval(first,
285: end);
286: // Iniciamos el nuevo intervalo de seleccionados
287: isSelectionInterval = true;
288: first = i;
289: end = i;
290: }
291: } else {
292: // Iniciamos el nuevo intervalo de seleccionados
293: isSelectionInterval = true;
294: first = i;
295: end = i;
296: }
297: } else {
298: if (first >= 0) // hay un intervalo ya abierto
299: {
300: if (!isSelectionInterval) {
301: end = i; // Uno más al intervalo continuo
302: } else {
303: // Cerramos el intervalo de seleccionados primero
304: addSelectionIntervalWithContigous(
305: first, end);
306: // Iniciamos el nuevo intervalo de NO seleccionados
307: isSelectionInterval = false;
308: first = i;
309: end = i;
310: }
311: } else {
312: // Iniciamos el nuevo intervalo de NO seleccionados
313: isSelectionInterval = false;
314: first = i;
315: end = i;
316: }
317: }
318: } else // No ha cambiado
319: {
320: // Este elemento no ha cambiado por tanto
321: // no vamos ni añadir ni a quitar de la selección
322: // por ello hemos de cerrar el intervalo que haya pendiente pues ya no hay continuidad
323: if (first >= 0) // Hay un intervalo pendiente
324: {
325: if (isSelectionInterval)
326: addSelectionIntervalWithContigous(first,
327: end);
328: else
329: selModel
330: .removeSelectionInterval(first, end);
331: }
332: first = -1;
333: end = -1;
334: }
335: } // Fin del for
336:
337: // Cerramos el intervalo que haya quedado pendiente de añadir/quitar
338: if (first >= 0) // Hay un intervalo pendiente
339: {
340: if (isSelectionInterval)
341: addSelectionIntervalWithContigous(first, end);
342: else
343: selModel.removeSelectionInterval(first, end);
344: }
345: } finally {
346: selModel.setValueIsAdjusting(false); // Envía un evento con todos los cambios
347: selModel.setValueIsAdjusting(oldAdjusting); // Restaura
348: }
349: }
350: }
|