View Javadoc

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 }