1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.gui.platform.swing.builder.components;
17
18 import java.awt.Point;
19 import java.awt.Rectangle;
20 import java.util.List;
21
22 import javax.swing.JScrollPane;
23 import javax.swing.JTable;
24 import javax.swing.JViewport;
25 import javax.swing.ListSelectionModel;
26 import javax.swing.event.ListSelectionEvent;
27 import javax.swing.event.ListSelectionListener;
28
29 import net.sf.jguiraffe.gui.builder.components.Color;
30 import net.sf.jguiraffe.gui.builder.components.model.TableHandler;
31 import net.sf.jguiraffe.gui.platform.swing.builder.components.table.SwingTableModel;
32
33 /**
34 * <p>
35 * The Swing-specific implementation of a component handler for a table.
36 * </p>
37 * <p>
38 * This class wraps a <code>javax.swing.JTable</code> component and implements
39 * the methods required for Swing component handlers in a suitable way. The
40 * following functionality is provided:
41 * <ul>
42 * <li>The data handling depends on the table's selection model. If the table
43 * supports single selection only, the handler's data type is an int value
44 * representing the selected row. Otherwise the type is an array of int values
45 * storing the indices of the selected rows. The <code>getData()</code> and
46 * <code>setData()</code> methods work correspondingly.</li>
47 * <li>Methods are available for directly querying and manipulating the tables
48 * selection. These methods are defined by the platform-neutral
49 * {@link TableHandler} interface.</li>
50 * <li>A set of methods support notifications when data in the table's model
51 * changes. These methods are also defined by the
52 * {@link TableHandler} interface. They are delegated to the
53 * (Swing) table model implementation.</li>
54 * <li>Platform-neutral change listeners can register at handlers of this type.
55 * They will then be notified whenever the table's selection changes.</li>
56 * <li>The colors of the table's selection can be queried and manipulated.</li>
57 * <li>A scroll pane for the table is automatically created and maintained.</li>
58 * </ul>
59 * </p>
60 *
61 * @author Oliver Heger
62 * @version $Id: SwingTableComponentHandler.java 205 2012-01-29 18:29:57Z oheger $
63 */
64 class SwingTableComponentHandler extends SwingComponentHandler<Object> implements
65 TableHandler, ListSelectionListener
66 {
67 /** Stores the table's scroll pane. */
68 private final JScrollPane scrollPane;
69
70 /**
71 * Creates a new instance of {@code SwingTableComponentHandler} and sets the
72 * wrapped table.
73 *
74 * @param table the wrapped table component
75 * @param scrollWidth the preferred scroll width
76 * @param scrollHeight the preferred scroll height
77 */
78 public SwingTableComponentHandler(JTable table, int scrollWidth,
79 int scrollHeight)
80 {
81 super(table);
82 scrollPane = SwingComponentUtils.scrollPaneFor(table, scrollWidth,
83 scrollHeight);
84 }
85
86 /**
87 * Returns the table component.
88 *
89 * @return the table
90 */
91 public JTable getTable()
92 {
93 return (JTable) getComponent();
94 }
95
96 /**
97 * Returns the table model.
98 *
99 * @return the table model
100 */
101 public SwingTableModel getTableModel()
102 {
103 return (SwingTableModel) getTable().getModel();
104 }
105
106 /**
107 * Returns a flag whether the wrapped table supports multi selection.
108 *
109 * @return the multi selection flag
110 */
111 public boolean isMultiSelection()
112 {
113 return getTable().getSelectionModel().getSelectionMode()
114 != ListSelectionModel.SINGLE_SELECTION;
115 }
116
117 /**
118 * Clears any information about selected rows.
119 */
120 public void clearSelection()
121 {
122 getTable().clearSelection();
123 }
124
125 /**
126 * Returns the index of the selected row. This method is intended to be used
127 * in single selection mode. If no row is selected, result will be -1.
128 *
129 * @return the (0-based) index of the selected row
130 */
131 public int getSelectedIndex()
132 {
133 return getTable().getSelectedRow();
134 }
135
136 /**
137 * Returns an array with the indices of the currently selected rows. This
138 * method is intended to be used in multi selection mode. If no rows are
139 * selected, an empty array will be returned.
140 *
141 * @return an array with the (0-based) indices of the selected rows
142 */
143 public int[] getSelectedIndices()
144 {
145 return getTable().getSelectedRows();
146 }
147
148 /**
149 * Notifies the table about deleted rows. This will cause a redraw if
150 * necessary.
151 *
152 * @param startIdx the start index of the affected row interval
153 * @param endIdx the end index of the affected row interval
154 */
155 public void rowsDeleted(int startIdx, int endIdx)
156 {
157 getTableModel().fireTableRowsDeleted(startIdx, endIdx);
158 }
159
160 /**
161 * Notifies the table about inserted rows. This will cause a redraw if
162 * necessary.
163 *
164 * @param startIdx the start index of the affected row interval
165 * @param endIdx the end index of the affected row interval
166 */
167 public void rowsInserted(int startIdx, int endIdx)
168 {
169 getTableModel().fireTableRowsInserted(startIdx, endIdx);
170 }
171
172 /**
173 * Notifies the table about updated rows. This will cause a redraw if
174 * necessary.
175 *
176 * @param startIdx the start index of the affected row interval
177 * @param endIdx the end index of the affected row interval
178 */
179 public void rowsUpdated(int startIdx, int endIdx)
180 {
181 getTableModel().fireTableRowsUpdated(startIdx, endIdx);
182 }
183
184 /**
185 * Sets the index of the selected row.
186 *
187 * @param rowIdx the (0-based) index of the row to select
188 */
189 public void setSelectedIndex(int rowIdx)
190 {
191 clearSelection();
192 if (rowIdx >= 0)
193 {
194 getTable().addRowSelectionInterval(rowIdx, rowIdx);
195 handleScrolling(scrollPane.getViewport(), rowIdx);
196 }
197 }
198
199 /**
200 * Selects a number of rows. This method is available in multi selection
201 * mode.
202 *
203 * @param rowIndices the indices of the rows to be selected
204 */
205 public void setSelectedIndices(int[] rowIndices)
206 {
207 clearSelection();
208 for (int row : rowIndices)
209 {
210 getTable().addRowSelectionInterval(row, row);
211 }
212 }
213
214 /**
215 * Notifies the table about an unspecific change of the data of its model.
216 * This will cause a redraw.
217 */
218 public void tableDataChanged()
219 {
220 getTableModel().fireTableDataChanged();
221 }
222
223 /**
224 * Returns the data of the underlying component. This is the table's
225 * selection. Depending on the selection mode (single or multi) either a
226 * single row index of an array of row indices is returned.
227 *
228 * @return the data of the represented component
229 */
230 public Object getData()
231 {
232 return isMultiSelection() ? getSelectedIndices() : getSelectedIndex();
233 }
234
235 /**
236 * Returns the outer most component. For tables this is the scroll pane the
237 * table is embedded.
238 *
239 * @return the outer component
240 */
241 @Override
242 public Object getOuterComponent()
243 {
244 return scrollPane;
245 }
246
247 /**
248 * Returns the type of this component handler. The type depends on the
249 * table's selection mode.
250 *
251 * @return the type of this handler
252 */
253 public Class<?> getType()
254 {
255 return isMultiSelection() ? int[].class : Integer.TYPE;
256 }
257
258 /**
259 * Sets the data of this component. Supported are objects of type
260 * <code>Number</code> or an array of int values. A value of <b>null</b>
261 * is also accepted, it will reset the selection.
262 *
263 * @param data the new data for the component
264 * @throws IllegalArgumentException if the passed in value is not supported
265 */
266 public void setData(Object data)
267 {
268 if (data == null)
269 {
270 clearSelection();
271 }
272 else if (data instanceof Number)
273 {
274 setSelectedIndex(((Number) data).intValue());
275 }
276 else if (data instanceof int[])
277 {
278 setSelectedIndices((int[]) data);
279 }
280 else
281 {
282 throw new IllegalArgumentException("Unsupported type for setData: "
283 + data);
284 }
285 }
286
287 /**
288 * Returns the model of this table. This is the list with the beans
289 * representing the rows of this table. It is obtained from the table model.
290 * Note that no defensive copy of the list is created, so it can be
291 * manipulated by callers directly. This is legal, but then the change
292 * methods like {@code rowsDeleted()} or {@code rowsUpdated()} should be
293 * called to notify the table about these external changes.
294 *
295 * @return the model of this table
296 */
297 public List<Object> getModel()
298 {
299 return getTableModel().getModelData();
300 }
301
302 /**
303 * Returns the selection background color. This implementation obtains the
304 * color from the underlying table and converts it to a platform-independent
305 * {@code Color} object.
306 *
307 * @return the selection background color
308 */
309 public Color getSelectionBackground()
310 {
311 return SwingComponentUtils.swing2LogicColor(getTable()
312 .getSelectionBackground());
313 }
314
315 /**
316 * Returns the selection foreground color. This implementation obtains the
317 * color from the underlying table and converts it to a platform-independent
318 * {@code Color} object.
319 *
320 * @return the selection foreground color
321 */
322 public Color getSelectionForeground()
323 {
324 return SwingComponentUtils.swing2LogicColor(getTable()
325 .getSelectionForeground());
326 }
327
328 /**
329 * Sets the selection background color. This implementation converts the
330 * specified {@code Color} object to the corresponding {@code
331 * java.awt.Color} and passes it to the underlying table.
332 *
333 * @param c the new selection background color
334 */
335 public void setSelectionBackground(Color c)
336 {
337 getTable().setSelectionBackground(SwingComponentUtils.logic2SwingColor(c));
338 }
339
340 /**
341 * Sets the selection foreground color. This implementation converts the
342 * specified {@code Color} object to the corresponding {@code
343 * java.awt.Color} and passes it to the underlying table.
344 *
345 * @param c the new selection foreground color
346 */
347 public void setSelectionForeground(Color c)
348 {
349 getTable().setSelectionForeground(SwingComponentUtils.logic2SwingColor(c));
350 }
351
352 /**
353 * Reacts on changes of the table's selection. This will cause registered
354 * change listeners to be triggered.
355 *
356 * @param e the list selection event
357 */
358 public void valueChanged(ListSelectionEvent e)
359 {
360 if (!e.getValueIsAdjusting())
361 {
362 fireChangeEvent(e);
363 }
364 }
365
366 /**
367 * Registers a change listener at the underlying component. This
368 * implementation registers this object itself as a list selection lister at
369 * the wrapped table's row selection model.
370 */
371 @Override
372 protected void registerChangeListener()
373 {
374 getTable().getSelectionModel().addListSelectionListener(this);
375 }
376
377 /**
378 * Unregisters this handler as change listener from the underlying component.
379 */
380 @Override
381 protected void unregisterChangeListener()
382 {
383 getTable().getSelectionModel().removeListSelectionListener(this);
384 }
385
386 /**
387 * Handles scrolling when a row is selected. This method ensures that the
388 * newly selected row becomes visible. However, the horizontal scrolling
389 * position should not be changed.
390 *
391 * @param vp the view port
392 * @param rowIdx the index of the row to be made visible
393 */
394 void handleScrolling(JViewport vp, int rowIdx)
395 {
396 Point orgPos = vp.getViewPosition();
397 Rectangle rect = getTable().getCellRect(rowIdx, 0, true);
398 getTable().scrollRectToVisible(rect);
399 updateViewport(vp, orgPos);
400 }
401
402 /**
403 * Updates the scroll position of the view port. This method is called when
404 * a row is selected. In this case the row should be made visible, but the
405 * horizontal position should not be changed. This method ensures that the X
406 * offset is restored if it was changed.
407 *
408 * @param vp the {@code JViewport}
409 * @param orgPos the original position of the {@code JViewport}
410 */
411 void updateViewport(JViewport vp, Point orgPos)
412 {
413 Point newPos = vp.getViewPosition();
414 if (orgPos.x != newPos.x)
415 {
416 vp.setViewPosition(new Point(orgPos.x, newPos.y));
417 }
418 }
419 }