1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.util.swing; |
|
27 |
|
28 import java.util.*; |
|
29 import javax.swing.*; |
|
30 import javax.swing.event.*; |
|
31 import com.oracle.solaris.vp.util.misc.CollectionUtil; |
|
32 |
|
33 /** |
|
34 * The {@code ListSelectionSynchronizer} class synchronizes two {@code |
|
35 * ListSelectionModel}s. Useful if two or more components share a data model. |
|
36 */ |
|
37 public class ListSelectionSynchronizer implements ListSelectionListener { |
|
38 // |
|
39 // Inner classes |
|
40 // |
|
41 |
|
42 public static interface IndexMapper { |
|
43 public int map(int i); |
|
44 } |
|
45 |
|
46 // |
|
47 // Instance data |
|
48 // |
|
49 |
|
50 private ListSelectionModel tModel; |
|
51 private IndexMapper mapper; |
|
52 |
|
53 // |
|
54 // Constructors |
|
55 // |
|
56 |
|
57 public ListSelectionSynchronizer( |
|
58 ListSelectionModel tModel, IndexMapper mapper) { |
|
59 |
|
60 this.tModel = tModel; |
|
61 this.mapper = mapper; |
|
62 } |
|
63 |
|
64 public ListSelectionSynchronizer(ListSelectionModel tModel) { |
|
65 this(tModel, null); |
|
66 } |
|
67 |
|
68 // |
|
69 // ListSelectionListener methods |
|
70 // |
|
71 |
|
72 @Override |
|
73 public void valueChanged(ListSelectionEvent e) { |
|
74 if (!e.getValueIsAdjusting()) { |
|
75 ListSelectionModel fModel = (ListSelectionModel)e.getSource(); |
|
76 List<Integer> fList = getSelectedIndexes(fModel, mapper); |
|
77 List<Integer> tList = getSelectedIndexes(tModel, null); |
|
78 |
|
79 List<Integer>[] interDiffs = |
|
80 CollectionUtil.getIntersectAndDiffs(tList, fList, null); |
|
81 |
|
82 List<Integer> toRemove = interDiffs[1]; |
|
83 List<Integer> toAdd = interDiffs[2]; |
|
84 |
|
85 if (!toRemove.isEmpty() || !toAdd.isEmpty()) { |
|
86 tModel.setValueIsAdjusting(true); |
|
87 addOrRemove(toRemove, false); |
|
88 addOrRemove(toAdd, true); |
|
89 tModel.setValueIsAdjusting(false); |
|
90 } |
|
91 } |
|
92 } |
|
93 |
|
94 // |
|
95 // Private methods |
|
96 // |
|
97 |
|
98 private void addOrRemove(List<Integer> iList, boolean add) { |
|
99 int n = iList.size(); |
|
100 |
|
101 if (n > 0) { |
|
102 int first = iList.get(0); |
|
103 |
|
104 for (int i = 1; i < n; i++) { |
|
105 int last = iList.get(i - 1); |
|
106 int current = iList.get(i); |
|
107 |
|
108 if (current != last + 1) { |
|
109 if (add) { |
|
110 tModel.addSelectionInterval(first, last); |
|
111 } else { |
|
112 tModel.removeSelectionInterval(first, last); |
|
113 } |
|
114 |
|
115 first = current; |
|
116 } |
|
117 } |
|
118 |
|
119 if (add) { |
|
120 tModel.addSelectionInterval(first, iList.get(n - 1)); |
|
121 } else { |
|
122 tModel.removeSelectionInterval(first, iList.get(n - 1)); |
|
123 } |
|
124 } |
|
125 } |
|
126 |
|
127 private List<Integer> getSelectedIndexes( |
|
128 ListSelectionModel sModel, IndexMapper mapper) { |
|
129 |
|
130 List<Integer> iList = new ArrayList<Integer>(); |
|
131 |
|
132 int iMin = sModel.getMinSelectionIndex(); |
|
133 int iMax = sModel.getMaxSelectionIndex(); |
|
134 |
|
135 for (int i = iMin; i <= iMax; i++) { |
|
136 if (sModel.isSelectedIndex(i)) { |
|
137 iList.add(mapper == null ? i : mapper.map(i)); |
|
138 } |
|
139 } |
|
140 |
|
141 if (mapper != null) { |
|
142 Collections.sort(iList); |
|
143 } |
|
144 |
|
145 return iList; |
|
146 } |
|
147 |
|
148 // |
|
149 // Static methods |
|
150 // |
|
151 |
|
152 /** |
|
153 * Synchronize a {@code JTable}'s selection model with that of {@code |
|
154 * JList}. Changes in selection of either will be reflected in the other. |
|
155 * </p> |
|
156 * Note: unexpected behavior may result if the selection mode differs |
|
157 * between the two. |
|
158 * |
|
159 * @param table |
|
160 * a {@code JTable} |
|
161 * |
|
162 * @param list |
|
163 * {@code JList} |
|
164 */ |
|
165 public static void syncSelection(final JTable table, JList list) { |
|
166 ListSelectionModel tsModel = table.getSelectionModel(); |
|
167 ListSelectionModel lModel = list.getSelectionModel(); |
|
168 |
|
169 // Sync changes to lModel with tsModel |
|
170 lModel.addListSelectionListener( |
|
171 new ListSelectionSynchronizer(tsModel, |
|
172 new ListSelectionSynchronizer.IndexMapper() { |
|
173 @Override |
|
174 public int map(int i) { |
|
175 return table.convertRowIndexToView(i); |
|
176 } |
|
177 })); |
|
178 |
|
179 // Sync changes to tsModel with lModel |
|
180 tsModel.addListSelectionListener( |
|
181 new ListSelectionSynchronizer(lModel, |
|
182 new ListSelectionSynchronizer.IndexMapper() { |
|
183 @Override |
|
184 public int map(int i) { |
|
185 return table.convertRowIndexToModel(i); |
|
186 } |
|
187 })); |
|
188 } |
|
189 |
|
190 /** |
|
191 * Synchronize two {@code JTable}s' selection models. Changes in selection |
|
192 * of either will be reflected in the other. |
|
193 * </p> |
|
194 * Note: unexpected behavior may result if the selection mode differs |
|
195 * between the two. |
|
196 * |
|
197 * @param table1 |
|
198 * a {@code JTable} |
|
199 * |
|
200 * @param table2 |
|
201 * a {@code JTable} |
|
202 */ |
|
203 public static void syncSelection( |
|
204 final JTable table1, final JTable table2) { |
|
205 |
|
206 ListSelectionModel model1 = table1.getSelectionModel(); |
|
207 ListSelectionModel model2 = table2.getSelectionModel(); |
|
208 |
|
209 // Sync changes to model2 with model1 |
|
210 model2.addListSelectionListener( |
|
211 new ListSelectionSynchronizer(model1, |
|
212 new ListSelectionSynchronizer.IndexMapper() { |
|
213 @Override |
|
214 public int map(int i) { |
|
215 return table1.convertRowIndexToView( |
|
216 table2.convertRowIndexToModel(i)); |
|
217 } |
|
218 })); |
|
219 |
|
220 // Sync changes to model1 with model2 |
|
221 model1.addListSelectionListener( |
|
222 new ListSelectionSynchronizer(model2, |
|
223 new ListSelectionSynchronizer.IndexMapper() { |
|
224 @Override |
|
225 public int map(int i) { |
|
226 return table2.convertRowIndexToView( |
|
227 table1.convertRowIndexToModel(i)); |
|
228 } |
|
229 })); |
|
230 } |
|
231 } |
|