View Javadoc

1   // START LICENSE
2   // JSet - a Java JSet card board game implementation
3   // Copyright (C) 2004 Jerome Lacoste
4   //
5   // This program is free software; you can redistribute it and/or modify
6   // it under the terms of the GNU General Public License as published by
7   // the Free Software Foundation; either version 2 of the License, or (at
8   // your option) any later version.
9   //
10  // This program is distributed in the hope that it will be useful, but
11  // WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // General Public License for more details.
14  //
15  // You should have received a copy of the GNU General Public License
16  // along with this program; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  // END LICENSE
19  
20  package org.cb.jset.client.ui;
21  
22  /***
23   * Created by IntelliJ IDEA.
24   * User: jerome
25   * Date: Jan 27, 2003
26   * Time: 12:09:22 AM
27   * To change this template use Options | File Templates.
28   */
29  
30  import org.cb.cardboard.AbstractCardBoardModel;
31  import org.cb.cardboard.BoardSelectionEvent;
32  import org.cb.cardboard.BoardSelectionListener;
33  import org.cb.cardboard.BoardSelectionModel;
34  import org.cb.cardboard.Card;
35  import org.cb.cardboard.CardBoardModel;
36  import org.cb.cardboard.DefaultBoardSelectionModel;
37  
38  import javax.swing.JPanel;
39  import javax.swing.ToolTipManager;
40  import java.io.Serializable;
41  import java.util.Arrays;
42  import java.util.Collections;
43  import java.util.List;
44  
45  /***
46   * The component representing the set board in the graphical client.
47   *
48   * FIXME think about using Accessible
49   *
50   * @author jerome@coffeebreaks.org - last modified by $LastChangedBy: jerome $
51   * @version $Id: JSetBoardComponent.java 130 2004-04-15 05:18:07Z jerome $
52   */
53  public class JSetBoardComponent extends JPanel
54  {
55  //  private Logger _logger = Logger.getLogger(this.getClass().getName());
56  
57  //  private final EventHandler _eventHandler = new EventHandler();
58  //  private CardBoardModel _model = new JSetClientBoardModel();
59  
60    // private final List _cards = new ArrayList();
61  
62  //  private final java.util.Map _cardComponents = new java.util.HashMap();
63  
64    private CardBoardModel _dataModel;
65    private BoardSelectionModel _selectionModel;
66    // private BoardCardRenderer _cardRenderer;
67    private BoardSelectionListener _selectionListener;
68  
69    /***
70     * Constructs a Board component using the specified dataModel.
71     * @param dataModel the model for that component
72     */
73    public JSetBoardComponent(final CardBoardModel dataModel)
74    {
75      if (dataModel == null)
76      {
77        throw new IllegalArgumentException("dataModel must be non null");
78      }
79  
80      // Register with the ToolTipManager so that tooltips from the
81      // renderer show through.
82      final ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
83      toolTipManager.registerComponent(this);
84  
85  //    layoutOrientation = VERTICAL;
86  
87      this._dataModel = dataModel;
88      _selectionModel = createSelectionModel();
89  
90  //    setAutoscrolls(true);
91  //    setOpaque(true);
92      updateUI();
93    }
94  
95    /***
96     * Constructs a <code>JCardBoardComponent</code> that displays the elements in
97     * the specified array.  This constructor just delegates to the
98     * <code>ListModel</code> constructor.
99     *
100    * @param listData the array of Objects to be loaded into the data model
101    */
102   public JSetBoardComponent(final Card[] listData)
103   {
104     this(new AbstractCardBoardModel()
105     {
106       public int getSize()
107       {
108         return listData.length;
109       }
110 
111       public Card getElementAt(final int i)
112       {
113         return listData[i];
114       }
115 
116       public List getElements()
117       {
118         return Collections.unmodifiableList(Arrays.asList(listData));
119       }
120     });
121   }
122 
123   private BoardSelectionModel createSelectionModel()
124   {
125     return new DefaultBoardSelectionModel();
126   }
127 
128   /***
129    * @return the model for that board.
130    */
131   public CardBoardModel getModel()
132   {
133     return _dataModel;
134   }
135 
136   /***
137    * @return the selection model for that board.
138    */
139   public BoardSelectionModel getSelectionModel()
140   {
141     return _selectionModel;
142   }
143 
144   /***
145    * Sets a new model for that board.
146    * A property change event with name "model" will be fired. 
147    * @param model the new model
148    */
149   public void setModel(final CardBoardModel model)
150   {
151     if (model != _dataModel)
152     {
153 //      model.removeBoardDataListener(_eventHandler);
154       final CardBoardModel oldModel = _dataModel;
155       _dataModel = model;
156 //      _dataModel.addBoardDataListener(_eventHandler);
157       clearSelection();
158       firePropertyChange("model", oldModel, _dataModel);
159     }
160   }
161 
162   /***
163    * Determines whether single-item or multiple-item
164    * selections are allowed.
165    * The following <code>selectionMode</code> values are allowed:
166    * <ul>
167    * <li> <code>BoardSelectionModel.SINGLE_SELECTION</code>
168    * Only one list index can be selected at a time.  In this
169    * mode the <code>setSelectionInterval</code> and
170    * <code>addSelectionInterval</code>
171    * methods are equivalent, and only the second index
172    * argument is used.
173    * <li> <code>BoardSelectionModel.SINGLE_INTERVAL_SELECTION</code>
174    * One contiguous index interval can be selected at a time.
175    * In this mode <code>setSelectionInterval</code> and
176    * <code>addSelectionInterval</code>
177    * are equivalent.
178    * <li> <code>BoardSelectionModel.MULTIPLE_INTERVAL_SELECTION</code>
179    * In this mode, there's no restriction on what can be selected.
180    * This is the default.
181    * </ul>
182    *
183    * @param selectionMode an integer specifying the type of selections
184    *                      that are permissible
185    * @beaninfo description: The selection mode.
186    * enum: SINGLE_SELECTION            BoardSelectionModel.SINGLE_SELECTION
187    * SINGLE_INTERVAL_SELECTION   BoardSelectionModel.SINGLE_INTERVAL_SELECTION
188    * MULTIPLE_INTERVAL_SELECTION BoardSelectionModel.MULTIPLE_INTERVAL_SELECTION
189    * @see #getSelectionMode
190    */
191   public void setSelectionMode(final int selectionMode)
192   {
193     getSelectionModel().setSelectionMode(selectionMode);
194   }
195 
196   /***
197    * Returns whether single-item or multiple-item selections are allowed.
198    *
199    * @return the value of the <code>selectionMode</code> property
200    * @see #setSelectionMode
201    */
202   public int getSelectionMode()
203   {
204     return getSelectionModel().getSelectionMode();
205   }
206 
207 
208   /***
209    * Returns the first index argument from the most recent
210    * <code>addSelectionModel</code> or <code>setSelectionInterval</code> call.
211    * This is a convenience method that just delegates to the
212    * <code>selectionModel</code>.
213    *
214    * @return the index that most recently anchored an interval selection
215    * @see BoardSelectionModel#getAnchorSelectionIndex
216    * @see #addSelectionInterval
217    * @see #setSelectionInterval
218    * @see #addBoardSelectionListener
219    */
220   public int getAnchorSelectionIndex()
221   {
222     return getSelectionModel().getAnchorSelectionIndex();
223   }
224 
225 
226   /***
227    * Returns the second index argument from the most recent
228    * <code>addSelectionInterval</code> or <code>setSelectionInterval</code>
229    * call.
230    * This is a convenience method that just  delegates to the
231    * <code>selectionModel</code>.
232    *
233    * @return the index that most recently ended a interval selection
234    * @beaninfo description: The lead selection index.
235    * @see BoardSelectionModel#getLeadSelectionIndex
236    * @see #addSelectionInterval
237    * @see #setSelectionInterval
238    * @see #addBoardSelectionListener
239    */
240   public int getLeadSelectionIndex()
241   {
242     return getSelectionModel().getLeadSelectionIndex();
243   }
244 
245 
246   /***
247    * Returns the smallest selected cell index.
248    * This is a convenience method that just delegates to the
249    * <code>selectionModel</code>.
250    *
251    * @return the smallest selected cell index
252    * @see BoardSelectionModel#getMinSelectionIndex
253    * @see #addBoardSelectionListener
254    */
255   public int getMinSelectionIndex()
256   {
257     return getSelectionModel().getMinSelectionIndex();
258   }
259 
260 
261   /***
262    * Returns the largest selected cell index.
263    * This is a convenience method that just delegates to the
264    * <code>selectionModel</code>.
265    *
266    * @return the largest selected cell index
267    * @see BoardSelectionModel#getMaxSelectionIndex
268    * @see #addBoardSelectionListener
269    */
270   public int getMaxSelectionIndex()
271   {
272     return getSelectionModel().getMaxSelectionIndex();
273   }
274 
275   /***
276    * Returns true if the specified index is selected.
277    * This is a convenience method that just delegates to the
278    * <code>selectionModel</code>.
279    *
280    * @param index index to be queried for selection state
281    * @return true if the specified index is selected
282    * @see BoardSelectionModel#isSelectedIndex
283    * @see #setSelectedIndex
284    * @see #addBoardSelectionListener
285    */
286   public boolean isSelectedIndex(final int index)
287   {
288     return getSelectionModel().isSelectedIndex(index);
289   }
290 
291   /***
292    * Returns true if nothing is selected.
293    * This is a convenience method that just delegates to the
294    * <code>selectionModel</code>.
295    *
296    * @return true if nothing is selected
297    * @see BoardSelectionModel#isSelectionEmpty
298    * @see #clearSelection
299    * @see #addBoardSelectionListener
300    */
301   public boolean isSelectionEmpty()
302   {
303     return getSelectionModel().isSelectionEmpty();
304   }
305 
306   /***
307    * Returns an array of all of the selected indices in increasing
308    * order.
309    *
310    * @return all of the selected indices, in increasing order
311    * @see #removeSelectionInterval
312    * @see #addBoardSelectionListener
313    */
314   public int[] getSelectedIndices()
315   {
316     final BoardSelectionModel sm = getSelectionModel();
317     final int iMin = sm.getMinSelectionIndex();
318     final int iMax = sm.getMaxSelectionIndex();
319 
320     if ((iMin < 0) || (iMax < 0))
321     {
322       return new int[0];
323     }
324 
325     final int[] rvTmp = new int[1 + (iMax - iMin)];
326     int n = 0;
327     for (int i = iMin; i <= iMax; i++)
328     {
329       if (sm.isSelectedIndex(i))
330       {
331         rvTmp[n++] = i;
332       }
333     }
334     final int[] rv = new int[n];
335     System.arraycopy(rvTmp, 0, rv, 0, n);
336     return rv;
337   }
338 
339   /***
340    * Selects a single cell.
341    *
342    * @param index the index of the one cell to select
343    * @beaninfo description: The index of the selected cell.
344    * @see BoardSelectionModel#setSelectionInterval
345    * @see #isSelectedIndex
346    * @see #addBoardSelectionListener
347    */
348   public void setSelectedIndex(final int index)
349   {
350     getSelectionModel().setSelectionInterval(index, index);
351   }
352 
353 
354   /***
355    * Selects a set of cells.
356    *
357    * @param indices an array of the indices of the cells to select
358    * @see BoardSelectionModel#addSelectionInterval
359    * @see #isSelectedIndex
360    * @see #addBoardSelectionListener
361    */
362   public void setSelectedIndices(final int[] indices)
363   {
364     final BoardSelectionModel sm = getSelectionModel();
365     sm.clearSelection();
366     for (int i = 0; i < indices.length; i++)
367     {
368       sm.addSelectionInterval(indices[i], indices[i]);
369     }
370   }
371 
372   /***
373    * Returns an array of the values for the selected cells.
374    * The returned values are sorted in increasing index order.
375    *
376    * @return the selected values or an empty list if
377    *         nothing is selected
378    * @see #isSelectedIndex
379    * @see #getModel
380    * @see #addBoardSelectionListener
381    */
382   public Object[] getSelectedValues()
383   {
384     final BoardSelectionModel sm = getSelectionModel();
385     final CardBoardModel dm = getModel();
386 
387     final int iMin = sm.getMinSelectionIndex();
388     final int iMax = sm.getMaxSelectionIndex();
389 
390     if ((iMin < 0) || (iMax < 0))
391     {
392       return new Object[0];
393     }
394 
395     final Object[] rvTmp = new Object[1 + (iMax - iMin)];
396     int n = 0;
397     for (int i = iMin; i <= iMax; i++)
398     {
399       if (sm.isSelectedIndex(i))
400       {
401         rvTmp[n++] = dm.getElementAt(i);
402       }
403     }
404     final Object[] rv = new Object[n];
405     System.arraycopy(rvTmp, 0, rv, 0, n);
406     return rv;
407   }
408 
409   /***
410    * Adds a listener to the cardBoard that's notified each time a change
411    * to the selection occurs.  Listeners added directly to the
412    * <code>JCardBoardComponent</code>
413    * will have their <code>ListSelectionEvent.getSource() ==
414    * this JCardBoardComponent</code>
415    * (instead of the <code>BoardSelectionModel</code>).
416    *
417    * @param listener the <code>ListSelectionListener</code> to add
418    * @see #getSelectionModel
419    * @see #getBoardSelectionListeners()
420    */
421   public void addBoardSelectionListener(final BoardSelectionListener listener)
422   {
423     if (_selectionListener == null)
424     {
425       _selectionListener = new BoardSelectionHandler();
426       getSelectionModel().addBoardSelectionListener(_selectionListener);
427     }
428 
429     listenerList.add(BoardSelectionListener.class, listener);
430   }
431 
432 
433   /***
434    * Removes a listener from the list that's notified each time a
435    * change to the selection occurs.
436    *
437    * @param listener the <code>BoardSelectionListener</code> to remove
438    * @see #addBoardSelectionListener
439    * @see #getSelectionModel
440    */
441   public void removeBoardSelectionListener(final BoardSelectionListener listener)
442   {
443     listenerList.remove(BoardSelectionListener.class, listener);
444   }
445 
446   /***
447    * Returns an array of all the <code>BoardSelectionListener</code>s added
448    * to this JCardBoardComponent with addBoardSelectionListener().
449    *
450    * @return all of the <code>BoardSelectionListener</code>s added or an empty
451    *         array if no listeners have been added
452    * @see #addBoardSelectionListener
453    * @since 1.4
454    */
455   public BoardSelectionListener[] getBoardSelectionListeners()
456   {
457     return (BoardSelectionListener[]) listenerList.getListeners(BoardSelectionListener.class);
458   }
459 
460 //  public void cardsAdded(CardProperties[] cards)
461 //  {
462 //    for (int i = 0; i < cards.length; i++)
463 //    {
464 //      CardProperties card = cards[i];
465 //      _cardComponents.put(card, getCardFor(card));
466 //    }
467 //    _model.addCards(cards);
468 //  }
469 //
470 //  public void setRemoved(CardSet set)
471 //  {
472 //    try
473 //    {
474 //      _model.removeSet(set);
475 //    }
476 //    catch (BoardException e)
477 //    {
478 //      throw new IllegalStateException("Set should be matching: " + e.getMessage());
479 //    }
480 //  }
481 //
482 //
483 //  JSetCard getCardFor(CardProperties cardProperties)
484 //  {
485 //    return new JSetCard(cardProperties);
486 //  }
487 
488   /* A ListSelectionListener that forwards ListSelectionEvents from
489    * the selectionModel to the JCardBoardComponent ListSelectionListeners.  The
490    * forwarded events only differ from the originals in that their
491    * source is the JCardBoardComponent instead of the selectionModel itself.
492    */
493   private class BoardSelectionHandler implements BoardSelectionListener, Serializable
494   {
495     public void valueChanged(final BoardSelectionEvent e)
496     {
497       fireSelectionValueChanged(e.getFirstIndex(), e.getLastIndex(), e.getValueIsAdjusting());
498     }
499   }
500 
501 
502   /***
503    * Notifies <code>JCardBoardComponent</code> <code>BoardSelectionListener</code>s that
504    * the selection model has changed.  It's used to forward
505    * <code>BoardSelectionEvents</code> from the <code>selectionModel</code>
506    * to the <code>BoardSelectionListener</code>s added directly to the
507    * <code>JCardBoardComponent</code>.
508    *
509    * @param firstIndex  the first selected index
510    * @param lastIndex   the last selected index
511    * @param isAdjusting true if multiple changes are being made
512    * @see #addBoardSelectionListener(org.cb.cardboard.BoardSelectionListener)
513    * @see #removeBoardSelectionListener(BoardSelectionListener)
514    * @see javax.swing.event.EventListenerList
515    */
516   protected void fireSelectionValueChanged(final int firstIndex, final int lastIndex,
517                                            final boolean isAdjusting)
518   {
519     final Object[] listeners = listenerList.getListenerList();
520     BoardSelectionEvent e = null;
521 
522     for (int i = listeners.length - 2; i >= 0; i -= 2)
523     {
524       if (listeners[i] == BoardSelectionListener.class)
525       {
526         if (e == null)
527         {
528           e = new BoardSelectionEvent(this, firstIndex, isAdjusting);
529         }
530         ((BoardSelectionListener) listeners[i + 1]).valueChanged(e);
531       }
532     }
533   }
534 
535 
536   /***
537    * Clears the selection - after calling this method
538    * <code>isSelectionEmpty</code> will return true.
539    * This is a convenience method that just delegates to the
540    * <code>selectionModel</code>.
541    *
542    * @see BoardSelectionModel#clearSelection
543    * @see #isSelectionEmpty
544    * @see #addBoardSelectionListener
545    */
546   public void clearSelection()
547   {
548     getSelectionModel().clearSelection();
549   }
550 
551 
552   /***
553    * Selects the specified interval.  Both the <code>anchor</code>
554    * and <code>lead</code> indices are included.  It's not
555    * necessary for <code>anchor</code> to be less than <code>lead</code>.
556    * This is a convenience method that just delegates to the
557    * <code>selectionModel</code>.
558    * The <code>DefaultBoardSelectionModel</code> implementation
559    * will do nothing if either <code>anchor</code> or
560    * <code>lead</code> are -1.
561    * If <code>anchor</code> or <code>lead</code> are less than -1,
562    * <code>IndexOutOfBoundsException</code> is thrown.
563    *
564    * @param anchor the first index to select
565    * @param lead   the last index to select
566    * @throws IndexOutOfBoundsException if either <code>anchor</code>
567    *                                   or <code>lead</code> are less than -1
568    * @see BoardSelectionModel#setSelectionInterval
569    * @see #addSelectionInterval
570    * @see #removeSelectionInterval
571    * @see #addBoardSelectionListener
572    */
573   public void setSelectionInterval(final int anchor, final int lead)
574   {
575     getSelectionModel().setSelectionInterval(anchor, lead);
576   }
577 
578   /***
579    * Sets the selection to be the union of the specified interval with current
580    * selection.  Both the anchor and lead indices are
581    * included.  It's not necessary for anchor to be less than lead.
582    * This is a convenience method that just delegates to the
583    * <code>selectionModel</code>.  The
584    * <code>DefaultBoardSelectionModel</code> implementation
585    * will do nothing if either <code>anchor</code> or
586    * <code>lead</code> are -1.
587    * If <code>anchor</code> or <code>lead</code> are less than -1,
588    * <code>IndexOutOfBoundsException</code> is thrown.
589    *
590    * @param anchor the first index to add to the selection
591    * @param lead   the last index to add to the selection
592    * @throws IndexOutOfBoundsException if either <code>anchor</code>
593    *                                   or <code>lead</code> are less than -1
594    * @see BoardSelectionModel#addSelectionInterval
595    * @see #setSelectionInterval
596    * @see #removeSelectionInterval
597    * @see #addBoardSelectionListener
598    */
599   public void addSelectionInterval(final int anchor, final int lead)
600   {
601     getSelectionModel().addSelectionInterval(anchor, lead);
602   }
603 
604 
605   /***
606    * Sets the selection to be the set difference of the specified interval
607    * and the current selection.  Both the <code>index0</code> and
608    * <code>index1</code> indices are removed.  It's not necessary for
609    * <code>index0</code> to be less than <code>index1</code>.
610    * This is a convenience method that just delegates to the
611    * <code>selectionModel</code>.
612    * The <code>DefaultBoardSelectionModel</code> implementation
613    * will do nothing if either <code>index0</code> or
614    * <code>index1</code> are -1.
615    * If <code>index0</code> or <code>index1</code> are less than -1,
616    * <code>IndexOutOfBoundsException</code> is thrown.
617    *
618    * @param index0 the first index to remove from the selection
619    * @param index1 the last index to remove from the selection
620    * @throws IndexOutOfBoundsException if either <code>index0</code>
621    *                                   or <code>index1</code> are less than -1
622    * @see BoardSelectionModel#removeSelectionInterval
623    * @see #setSelectionInterval
624    * @see #addSelectionInterval
625    * @see #addBoardSelectionListener
626    */
627   public void removeSelectionInterval(final int index0, final int index1)
628   {
629     getSelectionModel().removeSelectionInterval(index0, index1);
630   }
631 
632 //  class EventHandler implements CardBoardModelListener
633 //  {
634 //    public void intervalAdded(CardBoardEvent e)
635 //    {
636 //      //To change body of implemented methods use File | Settings | File Templates.
637 //    }
638 //
639 //    public void intervalRemoved(CardBoardEvent e)
640 //    {
641 //      //To change body of implemented methods use File | Settings | File Templates.
642 //    }
643 //
644 //    public void contentsChanged(CardBoardEvent event)
645 //    {
646 //
647 //    }
648 //  }
649 }