1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.cb.jset.client.ui;
21
22 import org.cb.jset.CardProperties;
23 import org.cb.jset.JSetCard;
24 import org.cb.jset.client.model.SetGameClientCardListener;
25 import org.cb.jset.client.model.CardEvent;
26 import org.apache.log4j.Logger;
27
28 import javax.swing.JPanel;
29 import javax.swing.JComponent;
30 import javax.swing.BorderFactory;
31 import javax.swing.border.Border;
32 import java.awt.Rectangle;
33 import java.awt.Color;
34 import java.awt.Paint;
35 import java.awt.Graphics;
36 import java.awt.Graphics2D;
37 import java.awt.Shape;
38 import java.awt.TexturePaint;
39 import java.awt.BasicStroke;
40 import java.awt.Stroke;
41 import java.awt.geom.RectangularShape;
42 import java.awt.geom.Area;
43 import java.awt.geom.Ellipse2D;
44 import java.awt.geom.GeneralPath;
45 import java.awt.event.MouseAdapter;
46 import java.awt.event.MouseEvent;
47 import java.awt.image.BufferedImage;
48 import java.util.HashMap;
49 import java.util.Map;
50 import java.io.Serializable;
51
52 /***
53 * The visual component representing a card on the board.
54 *
55 * @author jerome@coffeebreaks.org - last modified by $LastChangedBy: jerome $
56 * @version $Id: JSetCardComponent.java 130 2004-04-15 05:18:07Z jerome $
57 */
58 public class JSetCardComponent extends JPanel
59 {
60 private final transient Logger _logger;
61 private ShapeComponent[] _shapes;
62
63 private boolean _isSelected = false;
64
65 private Border _selectedBorder = BorderFactory.createLineBorder(Color.black, 2);
66 private Border _unselectedBorder = BorderFactory.createRaisedBevelBorder();
67 private EventHandler _eventHandler = new EventHandler();
68 private JSetCard _card;
69
70 /***
71 * A class representing the third type of shape.
72 * @todo make it a subclass of RectangularShape ?
73 */
74 class OddShape
75 {
76
77 private final GeneralPath _oddShape;
78
79
80 /***
81 * Constructs an instance of <code>OddShape</code>.
82 */
83 public OddShape()
84 {
85 _oddShape = new GeneralPath();
86
87
88 }
89
90 /***
91 * Return a general path that walks throught the contour of the odd shape, given the specified bounds.
92 * @param bounds
93 * @return the general path of the odd shape
94 */
95 GeneralPath constructOddShape(final Rectangle bounds)
96 {
97 _logger.debug("building odd shape " + bounds);
98
99
100
101
102
103
104
105
106
107
108
109 final int margin = 8;
110
111 final int x0 = (int) bounds.getX() + margin;
112 final int y0 = (int) bounds.getY();
113 _oddShape.moveTo(x0, y0);
114 final int x1 = x0 + (int) bounds.getWidth() - 2 * margin;
115 final int y1 = y0;
116 _oddShape.lineTo(x1, y1);
117
118 final int x3 = x1;
119 final int y3 = y1 + (int) bounds.getHeight();
120 final int bez1x1 = x1 + 15;
121 final int bez1y1 = y1 + 10;
122 final int bez1x2 = x1 - 15;
123 final int bez1y2 = y3 - 10;
124 _oddShape.curveTo(bez1x1, bez1y1, bez1x2, bez1y2, x3, y3);
125
126 final int x2 = x0;
127 final int y2 = y3;
128 _oddShape.lineTo(x2, y2);
129
130
131 final int bez2x1 = x2 - 15;
132 final int bez2y1 = y2 - 10;
133 final int bez2x2 = x2 + 15;
134 final int bez2y2 = y0 + 10;
135 _oddShape.curveTo(bez2x1, bez2y1, bez2x2, bez2y2, x0, y0);
136
137
138 return _oddShape;
139 }
140 }
141
142 /***
143 * A graphical component representing the shapes on the card.
144 */
145 final class ShapeComponent extends JComponent
146 {
147 private final Color _color;
148 private final CardProperties _cardProperties;
149 private final transient Logger _logger;
150 private Map _shapes;
151
152 /***
153 * Constructs an instance of <code>ShapeComponent</code> given the specified card properties, and index
154 * on the card.
155 * @param cardProperties the properties used to determine the shape
156 * @param index the index used to determine the position.
157 */
158 private ShapeComponent(final CardProperties cardProperties, final int index)
159 {
160 _cardProperties = cardProperties;
161 _color = getColor(_cardProperties.getColor());
162 _logger = Logger.getLogger("Shape " + index + " " + cardProperties);
163 setBackground(Color.white);
164 }
165
166 private Color getColor(final byte pColor)
167 {
168 switch(pColor)
169 {
170 case CardProperties.COLOR_RED:
171 return Color.RED;
172 case CardProperties.COLOR_GREEN:
173 return new Color(0, 102, 0);
174 case CardProperties.COLOR_BLUE:
175 return Color.BLUE;
176 default:
177 throw new IllegalStateException("Invalid color " + pColor);
178 }
179 }
180
181 /***
182 * @inheritDoc
183 */
184 protected void paintComponent(final Graphics g)
185 {
186 _logger.debug("painting shape component: " + getBounds());
187 super.paintComponent(g);
188
189 final Graphics2D g2 = (Graphics2D) g;
190
191 _logger.debug("this bounds: " + getBounds());
192 _logger.debug("container bounds: " + getParent().getBounds());
193
194 final Paint oldPaint = g2.getPaint();
195
196 switch (_cardProperties.getFill())
197 {
198 case CardProperties.FILL_EMPTY :
199 case CardProperties.FILL_FULL :
200 g2.setPaint(_color);
201 break;
202 case CardProperties.FILL_DOTTED:
203 final BufferedImage bi = new BufferedImage(5, 5, BufferedImage.TYPE_INT_RGB);
204 final Graphics2D big = bi.createGraphics();
205 big.setColor(_color);
206 big.fillRect(0, 0, 5, 5);
207 big.setColor(getParent().getBackground());
208 big.fillOval(0, 0, 5, 5);
209 final Rectangle r = new Rectangle(0,0,5,5);
210 g2.setPaint(new TexturePaint(bi, r));
211 break;
212 }
213
214
215 switch (_cardProperties.getFill())
216 {
217 case CardProperties.FILL_EMPTY:
218 final Stroke biggerStroke = new BasicStroke(4.0f);
219 g2.setStroke(biggerStroke);
220 break;
221 case CardProperties.FILL_FULL :
222 break;
223 case CardProperties.FILL_DOTTED:
224 break;
225 }
226
227 final Shape shape = getShape(_cardProperties.getShape());
228 _logger.debug("%%% shape: " + shape + " bounds " + shape.getBounds());
229
230
231 switch (_cardProperties.getFill())
232 {
233 case CardProperties.FILL_FULL :
234 g2.fill(shape);
235 break;
236 case CardProperties.FILL_DOTTED:
237 final Graphics2D tempg2 = g2;
238 g2.fill(shape);
239 g2.setColor(Color.darkGray);
240 g2.draw(shape);
241 g2.setPaint(tempg2.getPaint());
242 break;
243 case CardProperties.FILL_EMPTY:
244 g2.draw(shape);
245 break;
246 }
247
248 _logger.debug("painting circle: " + getSize());
249
250 g2.setPaint(oldPaint);
251 }
252
253 private Map initShapes()
254 {
255 final Map shapes = new HashMap(3);
256
257 final Rectangle rectangle = new Rectangle();
258 final Rectangle internRectangle = new Rectangle();
259 shapes.put(new Integer(CardProperties.SHAPE_RECTANGLE), createShape(rectangle, internRectangle));
260
261 final Ellipse2D.Double oval = new Ellipse2D.Double();
262 final Ellipse2D.Double internOval = new Ellipse2D.Double();
263 shapes.put(new Integer(CardProperties.SHAPE_OVALE), createShape(oval, internOval));
264
265 final Rectangle lShapeBounds =
266 new Rectangle(0, 0, (int) getBounds().getWidth(), (int) getBounds().getHeight());
267 final Shape odd = new OddShape().constructOddShape(lShapeBounds);
268 shapes.put(new Integer(CardProperties.SHAPE_ODD), odd);
269
270 return shapes;
271 }
272
273 /***
274 * Create a shape by {@link Area#subtract(java.awt.geom.Area) substracting} the specified
275 * internal shape to the specified external one.
276 * @param externalShape
277 * @param internShape
278 * @return
279 */
280 private Area createShape(final RectangularShape externalShape, final RectangularShape internShape)
281 {
282 externalShape.setFrame(0, 0, getBounds().getWidth(), getBounds().getHeight());
283
284 final int xxxX = (int) (getBounds().getWidth() / 4);
285 final int xxxY = (int) (getBounds().getHeight() / 4);
286 internShape.setFrame(xxxX, xxxY, getBounds().getWidth() - 2 * xxxX, getBounds().getHeight() - 2 * xxxY);
287 final Area area2 = new Area(externalShape);
288 final Area internOv = new Area(internShape);
289 area2.subtract(internOv);
290 return area2;
291 }
292
293 private Shape getShape(final byte shapeID)
294 {
295
296 if (_shapes == null)
297 {
298 _shapes = this.initShapes();
299 }
300 return (Shape) _shapes.get(new Integer(shapeID));
301 }
302 }
303
304 /***
305 * Constructs an instance of <code>JSetCardComponent</code> given the specified card.
306 * @param card
307 */
308 public JSetCardComponent(final JSetCard card)
309 {
310 _logger = Logger.getLogger("Shape " + card);
311 _card = card;
312 _shapes = initShapes(card.getProperties());
313 for (int i = 0; i < _shapes.length; i++)
314 {
315 final ShapeComponent mShape = _shapes[i];
316 this.add(mShape);
317 }
318 init();
319 }
320
321 private void init()
322 {
323 this.setBackground(Color.white);
324 this.addMouseListener(_eventHandler);
325 this.setBorder(_unselectedBorder);
326 final int width = 80;
327 final int height = (int) (1.5 * width);
328 setSize(width, height);
329 setPreferredSize(getSize());
330 _logger.debug("initialized: " + getSize());
331 }
332
333 private ShapeComponent[] initShapes(final CardProperties pProperties)
334 {
335 final ShapeComponent[] lResult = new ShapeComponent[pProperties.getNumber()];
336 for (int i = 0; i < lResult.length; i++)
337 {
338 lResult[i] = new ShapeComponent(pProperties, i);
339 }
340 return lResult;
341 }
342
343
344 private void resetShapeBounds()
345 {
346 final int width = (int) this.getBounds().getWidth();
347 final int height = (int) this.getBounds().getHeight();
348
349 final int sideMargin = 5;
350 final int middleMargin = 5;
351 final int shapeWidth = (width - 2 * sideMargin - middleMargin) / 2;
352 final int shapeHeight = (height - 2 * sideMargin - middleMargin) / 2;
353 _logger.debug("** setting new shapes bounds: " + this.getBounds());
354 _logger.debug("** shapeWidth: " + shapeWidth + " shapeHeight: " + shapeHeight);
355
356 switch (_shapes.length)
357 {
358 case 1:
359 _shapes[0].setBounds((width - shapeWidth) / 2, (height - shapeHeight) / 2, shapeWidth, shapeHeight);
360 break;
361 case 2:
362
363 _shapes[0].setBounds((width - shapeWidth) / 2, sideMargin, shapeWidth, shapeHeight);
364
365 _shapes[1].setBounds((width - shapeWidth) / 2, sideMargin + shapeHeight + middleMargin, shapeWidth, shapeHeight);
366 break;
367 case 3:
368
369 _shapes[0].setBounds(sideMargin, sideMargin, shapeWidth, shapeHeight);
370 _shapes[1].setBounds(sideMargin + shapeWidth + middleMargin, sideMargin, shapeWidth, shapeHeight);
371
372 _shapes[2].setBounds((width - shapeWidth) / 2, sideMargin + shapeHeight + middleMargin, shapeWidth, shapeHeight);
373 break;
374 default:
375 throw new IllegalStateException("Invalid number of shapes " + _shapes.length);
376 }
377 for (int i = 0; i < _shapes.length; i++)
378 {
379 final ShapeComponent mShape = _shapes[i];
380 _logger.debug("shape[" + i + "]s bounds: " + mShape.getBounds());
381 }
382 }
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411 /***
412 * Update the border depending on the internal selection status.
413 * @see #isSelected
414 */
415 public void updateBorder()
416 {
417 if (_isSelected)
418 {
419 setBorder(_selectedBorder);
420 }
421 else
422 {
423 setBorder(_unselectedBorder);
424 }
425 invalidate();
426 }
427
428 /***
429 * Modifies the internal selection status
430 */
431 private void changeSelection()
432 {
433 setSelection(! _isSelected);
434 }
435
436 /***
437 * Set the new internal selection status for the card.
438 * If the selection has changed, fire an new {@link CardEvent} with the appropriate event type.
439 * @param newSelection
440 * @see #isSelected()
441 */
442 public void setSelection(final boolean newSelection)
443 {
444 final boolean oldSelection = _isSelected;
445 if (oldSelection != newSelection)
446 {
447 _isSelected = newSelection;
448 final int selected = _isSelected ? CardEvent.SELECTED : CardEvent.DESELECTED;
449 fireCardSelectionEvent(new CardEvent(getCard(), selected));
450 }
451 }
452
453 /***
454 * @return <code>true</code> if the card is selected, <code>false</code> otherwise.
455 */
456 public boolean isSelected()
457 {
458 return _isSelected;
459 }
460
461 /***
462 * @return the card
463 */
464 public JSetCard getCard()
465 {
466 return _card;
467 }
468
469 /***
470 * Paint this card component given the specified graphics.
471 * @param g
472 */
473 protected void paintComponent(final Graphics g)
474 {
475 _logger.debug("painting card component: " + getBounds());
476 super.paintComponent(g);
477 resetShapeBounds();
478 updateBorder();
479 }
480
481 /***
482 * @param i
483 * @return the ith shape from that card.
484 */
485 ShapeComponent getShape(int i)
486 {
487 return _shapes[i];
488 }
489
490 /***
491 * A mouse listener that {@link JSetCardComponent#changeSelection changes the card's selection} upon a mouse click.
492 */
493 private class EventHandler extends MouseAdapter implements Serializable
494 {
495 /***
496 * @inheritDoc
497 */
498 public void mouseClicked(final MouseEvent e)
499 {
500
501
502 if(JSetCardComponent.this.contains(e.getX(), e.getY()))
503 {
504 changeSelection();
505 }
506 }
507 }
508
509 /***
510 * Adds the specified cardlistener to the component listener list.
511 * @param cardListener
512 */
513 void addCardEventListener(final SetGameClientCardListener cardListener)
514 {
515 super.listenerList.add(SetGameClientCardListener.class, cardListener);
516 }
517
518 /***
519 * Removes the specified cardlistener to the component listener list.
520 * @param cardListener
521 */
522 void removeCardEventListener(final SetGameClientCardListener cardListener)
523 {
524 super.listenerList.remove(SetGameClientCardListener.class, cardListener);
525 }
526
527 /*** fire the specified card selection event
528 * @param e the event to fire
529 * @see SetGameClientCardListener#notifyCardEvent(org.cb.jset.client.model.CardEvent)
530 */
531 private void fireCardSelectionEvent(final CardEvent e)
532 {
533
534 final Object[] listeners = super.listenerList.getListenerList();
535 _logger.debug("" + listeners.length);
536
537
538 for (int i = listeners.length - 2; i >= 0; i -= 2)
539 {
540 if (listeners[i] == SetGameClientCardListener.class)
541 {
542 ((SetGameClientCardListener) listeners[i + 1]).notifyCardEvent(e);
543 }
544 }
545 }
546 }