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 org.itsnat.comp.ui.ItsNatTreeUI;
017: import org.itsnat.core.ItsNatException;
018: import javax.swing.DefaultListSelectionModel;
019: import javax.swing.ListSelectionModel;
020: import javax.swing.event.ListSelectionEvent;
021: import javax.swing.event.ListSelectionListener;
022: import javax.swing.event.TreeSelectionEvent;
023: import javax.swing.event.TreeSelectionListener;
024: import javax.swing.tree.TreeModel;
025: import javax.swing.tree.TreePath;
026: import javax.swing.tree.TreeSelectionModel;
027:
028: /**
029: * El modelo de selección de los nodos de un árbol es como una lista
030: * considerando los nodos como filas (rows)
031: *
032: * @author jmarranz
033: */
034: public class TreeSelectionModelMgrImpl implements
035: TreeSelectionListener, ListSelectionListener {
036: protected ItsNatTreeImpl tree;
037: protected TreeSelectionModel treeSelection;
038: protected ListSelectionModelMgrImpl listSelMgr = new ListSelectionModelMgrImpl(
039: new DefaultListSelectionModel()); // Es interno no debe usarse fuera de la clase
040: protected boolean synchTreeModelFromListModel = false; // temporalmente puede estar a true pero debe devolverse a false
041: protected boolean synchListModelFromTreeModel = true; // idem pero al revés
042: protected int currentMode = -1;
043:
044: /**
045: * Creates a new instance of TreeSelectionModelMgrImpl
046: */
047: public TreeSelectionModelMgrImpl(ItsNatTreeImpl tree,
048: TreeSelectionModel treeSelection) {
049: if (treeSelection == null)
050: treeSelection = EmptyTreeSelectionModelImpl.SINGLETON;
051:
052: this .tree = tree;
053: this .treeSelection = treeSelection;
054:
055: treeSelection.addTreeSelectionListener(this );
056: listSelMgr.getListSelectionModel().addListSelectionListener(
057: this );
058:
059: syncSelectionMode();
060:
061: //syncSelectionModelWithDataModel();
062: }
063:
064: public void dispose() {
065: treeSelection.removeTreeSelectionListener(this );
066: listSelMgr.dispose();
067: }
068:
069: public TreeSelectionModel getTreeSelectionModel() {
070: return treeSelection;
071: }
072:
073: private ListSelectionModelMgrImpl getListSelectionModelMgr() {
074: // NO usar el ListSelectionModel fuera de esta clase
075:
076: syncSelectionMode();
077:
078: return listSelMgr;
079: }
080:
081: private void syncSelectionMode() {
082: int newMode = treeSelection.getSelectionMode();
083: if (currentMode != newMode) {
084: this .currentMode = newMode;
085: int newListMode;
086: switch (newMode) {
087: case TreeSelectionModel.SINGLE_TREE_SELECTION:
088: newListMode = ListSelectionModel.SINGLE_SELECTION;
089: break;
090: case TreeSelectionModel.CONTIGUOUS_TREE_SELECTION:
091: newListMode = ListSelectionModel.SINGLE_INTERVAL_SELECTION;
092: break;
093: case TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION:
094: newListMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
095: break;
096: default:
097: throw new ItsNatException("INTERNAL ERROR");
098: }
099:
100: listSelMgr.getListSelectionModel().setSelectionMode(
101: newListMode);
102: }
103: }
104:
105: public void syncSelectionModelWithDataModel() {
106: removeAllUpdateModel();
107:
108: int size = tree.getRowCount();
109: // synchTreeModelFromListModel debe ser false
110:
111: getListSelectionModelMgr().syncWithDataModel(size);
112: }
113:
114: public boolean isRowSelected(int row) {
115: return getListSelectionModelMgr().getListSelectionModel()
116: .isSelectedIndex(row);
117: }
118:
119: public void changeSelectionModel(int row, boolean toggle,
120: boolean extend, boolean selected) {
121: // A través del list model actualizaremos después el tree model
122:
123: // El comportamiento de la selección en un JList/JTable es básicamente
124: // el mismo que en un JTree:
125: // BasicTreeUI.selectPathForEvent(TreePath path, MouseEvent event)
126: this .synchTreeModelFromListModel = true;
127: // los cambios se notificarán a través de eventos
128: getListSelectionModelMgr().changeSelectionModel(row, toggle,
129: extend, selected);
130: this .synchTreeModelFromListModel = false;
131: }
132:
133: public void valueChanged(TreeSelectionEvent e) {
134: if (tree.isExpandsSelectedPaths()) {
135: TreePath[] paths = e.getPaths();
136: for (int i = 0; i < paths.length; i++)
137: tree.expandPath(paths[i]);
138: }
139:
140: // Actualizar el list sel. model con los cambios en el modelo
141: if (!synchListModelFromTreeModel)
142: return;
143:
144: boolean oldState = synchTreeModelFromListModel;
145: this .synchTreeModelFromListModel = false; // por si acaso (actualización recursiva al cambiar el list selection)
146:
147: ListSelectionModelMgrImpl listSelModelMgr = getListSelectionModelMgr();
148: ListSelectionModel listSelection = listSelModelMgr
149: .getListSelectionModel();
150:
151: TreePath[] paths = e.getPaths();
152: int[] rows = tree.getRowsForPaths(paths);
153:
154: boolean oldAdjusting = listSelection.getValueIsAdjusting();
155: listSelection.setValueIsAdjusting(true); // Evita procesar muchos eventos para procesar al final todos en uno
156: try {
157: for (int i = 0; i < paths.length; i++) {
158: int row = rows[i];
159:
160: if (row >= 0) {
161: if (e.isAddedPath(i)) // es más eficiente que pasar el TreePath
162: {
163: listSelModelMgr
164: .addSelectionIntervalWithContigous(row,
165: row);
166: //listSelection.addSelectionInterval(row,row);
167: } else {
168: listSelection.removeSelectionInterval(row, row);
169: }
170: } else // No se visualiza por tanto no puede estar seleccionado
171: {
172: throw new ItsNatException(
173: "A hidden node can not be selected/unselected");
174: }
175: }
176: } finally {
177: listSelection.setValueIsAdjusting(false); // Envía un único evento con todos los cambios
178: listSelection.setValueIsAdjusting(oldAdjusting); // Restaura
179: }
180:
181: this .synchTreeModelFromListModel = oldState;
182: }
183:
184: public void valueChanged(ListSelectionEvent e) {
185: // Actualizar el tree sel. model con los cambios
186: if (!synchTreeModelFromListModel)
187: return;
188:
189: ListSelectionModel listSelection = getListSelectionModelMgr()
190: .getListSelectionModel();
191: if (listSelection.getValueIsAdjusting())
192: return;
193:
194: boolean oldState = synchListModelFromTreeModel;
195: this .synchListModelFromTreeModel = false; // por si acaso (actualización recursiva al cambiar el tree selection)
196:
197: if (listSelection.isSelectionEmpty())
198: treeSelection.clearSelection();
199: else {
200: if (treeSelection.getSelectionMode() == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) {
201: // En este caso no podemos ir uno a uno porque al quitar uno podemos
202: // quitar otro recién añadido al romperse temporalmente la continuidad (cuando un posible siguiente también será removido)
203: if (listSelection.isSelectionEmpty())
204: treeSelection.clearSelection();
205: else {
206: int first = listSelection.getMinSelectionIndex();
207: int last = listSelection.getMaxSelectionIndex();
208: TreePath[] paths = new TreePath[last - first + 1];
209: for (int i = first; i <= last; i++) {
210: TreePath path = tree.getTreePathForRow(i);
211: paths[i - first] = path;
212: }
213: treeSelection.setSelectionPaths(paths); // Así si apenas cambia uno será lo suficientemente inteligente para notificar sólo ese cambio
214: }
215: } else {
216: // Resto de los casos (single, multiple no continuo)
217: int firstRow = e.getFirstIndex();
218: int lastRow = e.getLastIndex();
219: for (int i = firstRow; i <= lastRow; i++) {
220: TreePath path = tree.getTreePathForRow(i);
221: // No debe ser null path, pues sería un signo de desincronización
222: boolean selected = listSelection.isSelectedIndex(i);
223: if (selected)
224: treeSelection.addSelectionPath(path);
225: else
226: treeSelection.removeSelectionPath(path);
227: }
228: }
229: }
230:
231: this .synchListModelFromTreeModel = oldState;
232: }
233:
234: public void insertElementUpdateModel(int i, TreePath parentPath) {
235: TreePath path = tree.toTreePath(i, parentPath);
236: insertElementUpdateModel(path);
237: }
238:
239: public void insertElementUpdateModel(TreePath path) {
240: int row = tree.getRowForPath(path);
241: if (row >= 0) {
242: getListSelectionModelMgr().insertElementUpdateModel(row);
243: // Ahora los hijos del nuevo nodo
244: insertAllChildElementsUpdateModel(path);
245: }
246: }
247:
248: public void insertAllChildElementsUpdateModel(TreePath parentPath) {
249: Object parentNode = parentPath.getLastPathComponent();
250: TreeModel dataModel = tree.getTreeModel();
251: int len = dataModel.getChildCount(parentNode);
252: for (int i = 0; i < len; i++) {
253: Object childNode = dataModel.getChild(parentNode, i);
254: //TreePath childPath = parentPath.pathByAddingChild(childNode);
255: insertElementUpdateModel(i, parentPath);
256: }
257: }
258:
259: public void removeElementUpdateModel(int i, Object childNode,
260: TreePath parentPath) {
261: int parentRow = tree.getRowForPath(parentPath);
262: if (parentRow == -1)
263: return;
264: TreePath childPath = parentPath.pathByAddingChild(childNode);
265: int rows = tree.getRowCountSubTree(childPath);
266: if (rows == 0)
267: return; // Si es 0 es que el nodo que se borra no se ve
268:
269: int rowStart = parentRow + i + 1;
270: int rowEnd = rowStart + rows - 1; // quitamos 1 pues se incluye el propio nodo en la cuenta de getRowCountSubTree()
271:
272: removeRangeUpdateModel(rowStart, rowEnd);
273: }
274:
275: private void removeRangeUpdateModel(int rowStart, int rowEnd) {
276: getListSelectionModelMgr().removeRangeUpdateModel(rowStart,
277: rowEnd);
278: }
279:
280: public void removeAllChildElementsUpdateModel(TreePath path) {
281: // Esta es la situación: los nodos por debajo de path
282: // han cambiado profundamente, puede que esté vacío o tenga
283: // nodos que no tienen nada que ver con lo que el selection
284: // recordaba, por lo que no tiene sentido
285: // recorrerlos, tenemos que quitar las rows que ocupaban los antiguos
286: // nodos como si path no tuviera hijos.
287: // Lo que hacemos es averiguar el row del nodo anterior
288: // y el posterior al path y así sabemos cuantos rows
289: // ocupaban tanto el propio path como los hijos
290:
291: if (path == null) // Es el caso del propio root
292: {
293: removeRootUpdateModel();
294: } else {
295: TreePath prevPath = tree.getPreviousPath(path);
296:
297: int rowStart, rowEnd;
298: if (prevPath == null) // es el root pues no hay hijo anterior ni padre (no hay nodo "antes")
299: {
300: int rootRow = tree.getRowForPath(path);
301: if (rootRow == -1)
302: return; // No es visible
303: rowStart = 1;
304: int rows = getListSelectionModelMgr().getSize();
305: rowEnd = rows - 1;
306: if (rowStart > rowEnd) // no tuvo hijos
307: return;
308: } else {
309: int rowPrev = tree.getRowForPath(prevPath);
310: if (rowPrev == -1) // Si el nodo anterior o el padre no tiene row (no se ve) el siguiente o el hijo tampoco, nada que hacer
311: return;
312: rowStart = rowPrev + 2; // pues el propio nodo no hay que quitarlo
313:
314: TreePath nextPath = tree.getNextPath(path, false); // los hijos no se incluyen
315: if (nextPath != null) {
316: int rowNext = tree.getRowForPath(nextPath);
317: // En este contexto rowNext no puede ser -1 aunque sea el siguiente del padre (o del padre del padre etc) pues path tiene row por tanto es visible (pensar)
318: rowEnd = rowNext - 1;
319: } else // Es el último, pero pudo tener hijos los cuales cuentan como rows
320: {
321: int rows = getListSelectionModelMgr().getSize();
322: rowEnd = rows - 1;
323: if (rowStart > rowEnd) // no tuvo hijos
324: return;
325: }
326: }
327:
328: removeRangeUpdateModel(rowStart, rowEnd);
329: }
330: }
331:
332: public void removeRootUpdateModel() {
333: boolean syncTreeOld = this .synchTreeModelFromListModel;
334: this .synchTreeModelFromListModel = false;
335:
336: getListSelectionModelMgr().removeAllUpdateModel();
337:
338: this .synchTreeModelFromListModel = syncTreeOld;
339:
340: boolean syncListOld = this .synchListModelFromTreeModel;
341: this .synchListModelFromTreeModel = false;
342:
343: treeSelection.clearSelection();
344:
345: this .synchListModelFromTreeModel = syncListOld;
346: }
347:
348: public void removeAllUpdateModel() {
349: // getListSelectionModelMgr().removeAllUpdateModel();
350: treeSelection.clearSelection(); // Yo creo que sobra pero por si acaso
351: }
352: }
|