View Javadoc

1   /*
2    * jcurl java curling software framework https://JCurl.mro.name Copyright (C)
3    * 2005-2009 M. Rohrmoser
4    * 
5    * This program is free software; you can redistribute it and/or modify it under
6    * the terms of the GNU General Public License as published by the Free Software
7    * Foundation; either version 2 of the License, or (at your option) any later
8    * version.
9    * 
10   * This program is distributed in the hope that it will be useful, but WITHOUT
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13   * details.
14   * 
15   * You should have received a copy of the GNU General Public License along with
16   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17   * Place, Suite 330, Boston, MA 02111-1307 USA
18   */
19  
20  package org.jcurl.demo.tactics.sg;
21  
22  import java.awt.BasicStroke;
23  import java.awt.Color;
24  import java.awt.Cursor;
25  import java.awt.Paint;
26  import java.awt.RenderingHints;
27  import java.awt.Shape;
28  import java.awt.Stroke;
29  import java.awt.event.MouseEvent;
30  import java.awt.geom.AffineTransform;
31  import java.awt.geom.Arc2D;
32  import java.awt.geom.Line2D;
33  import java.awt.geom.Point2D;
34  import java.beans.PropertyChangeEvent;
35  import java.beans.PropertyChangeListener;
36  
37  import javax.swing.BoundedRangeModel;
38  import javax.swing.event.ChangeEvent;
39  import javax.swing.event.ChangeListener;
40  
41  import org.apache.commons.logging.Log;
42  import org.jcurl.core.api.IceSize;
43  import org.jcurl.core.api.RockProps;
44  import org.jcurl.core.api.RockSet;
45  import org.jcurl.core.log.JCLoggerFactory;
46  import org.jcurl.core.ui.BroomPromptModel;
47  import org.jcurl.core.ui.ChangeManager;
48  import org.jcurl.core.ui.IceShapes;
49  import org.jcurl.core.ui.BroomPromptModel.HandleMemento;
50  import org.jcurl.core.ui.BroomPromptModel.SplitMemento;
51  import org.jcurl.core.ui.BroomPromptModel.XYMemento;
52  import org.jcurl.demo.tactics.HasChanger;
53  import org.jcurl.math.MathVec;
54  
55  import com.sun.scenario.scenegraph.SGGroup;
56  import com.sun.scenario.scenegraph.SGNode;
57  import com.sun.scenario.scenegraph.SGShape;
58  import com.sun.scenario.scenegraph.SGTransform;
59  import com.sun.scenario.scenegraph.SGAbstractShape.Mode;
60  import com.sun.scenario.scenegraph.SGTransform.Affine;
61  import com.sun.scenario.scenegraph.event.SGMouseAdapter;
62  
63  /**
64   * @author <a href="mailto:JCurl@mro.name">M. Rohrmoser </a>
65   * @version $Id$
66   */
67  public class BroomPromptScenario implements PropertyChangeListener,
68  		ChangeListener, HasChanger {
69  
70  	private class MoveHandler extends SGMouseAdapter {
71  		private XYMemento first = null;
72  
73  		private XYMemento create(final MouseEvent e, final SGNode node) {
74  			final Point2D p = dc2wc.globalToLocal(e.getPoint(), tmp);
75  			return new XYMemento(model, p);
76  		}
77  
78  		@Override
79  		public void mouseClicked(final MouseEvent e, final SGNode node) {
80  			if (e.getClickCount() != 2)
81  				return;
82  			final HandleMemento pre = new HandleMemento(model, model
83  					.getOutTurn());
84  			final HandleMemento post = new HandleMemento(model, !model
85  					.getOutTurn());
86  			getChanger().undoable(pre, post);
87  		}
88  
89  		@Override
90  		public void mouseDragged(final MouseEvent e, final SGNode node) {
91  			if (first == null)
92  				first = new XYMemento(model, model.getBroom());
93  			getChanger().temporary(create(e, node));
94  		}
95  
96  		@Override
97  		public void mouseReleased(final MouseEvent e, final SGNode node) {
98  			if (first != null)
99  				getChanger().undoable(first, create(e, node));
100 			first = null;
101 		}
102 	}
103 
104 	private class SpeedHandler extends SGMouseAdapter {
105 		private SplitMemento first = null;
106 
107 		private SplitMemento create(final MouseEvent e, final SGNode node) {
108 			final Point2D p = handle.globalToLocal(e.getPoint(), tmp);
109 			double f = 1 - p.getY() / stickLength;
110 			if (f > 1)
111 				f = 1;
112 			if (f < 0)
113 				f = 0;
114 			final BoundedRangeModel brm = model.getSplitTimeMillis();
115 			final int v = (int) (brm.getMinimum() + f
116 					* (brm.getMaximum() - brm.getMinimum()));
117 			return new SplitMemento(model, v);
118 		}
119 
120 		@Override
121 		public void mouseClicked(final MouseEvent e, final SGNode node) {
122 			if (e.getClickCount() != 2)
123 				return;
124 			final HandleMemento pre = new HandleMemento(model, model
125 					.getOutTurn());
126 			final HandleMemento post = new HandleMemento(model, !model
127 					.getOutTurn());
128 			getChanger().undoable(pre, post);
129 		}
130 
131 		@Override
132 		public void mouseDragged(final MouseEvent e, final SGNode node) {
133 			if (first == null)
134 				first = new SplitMemento(model, model.getSplitTimeMillis()
135 						.getValue());
136 			// create(e, node);
137 			getChanger().temporary(create(e, node));
138 		}
139 
140 		@Override
141 		public void mouseReleased(final MouseEvent e, final SGNode node) {
142 			if (first != null)
143 				getChanger().undoable(first, create(e, node));
144 			first = null;
145 		}
146 	}
147 
148 	private static final Log log = JCLoggerFactory
149 			.getLogger(BroomPromptScenario.class);
150 
151 	private static final Cursor moveC = Cursor
152 			.getPredefinedCursor(Cursor.MOVE_CURSOR);
153 
154 	private static final double scale0 = 0;
155 	private static final int scale50 = 50;
156 
157 	static SGShape node(final Shape sh, final Stroke st, final Paint sp,
158 			final double minScale) {
159 		final SGShape s = new SGShape();
160 		s.setShape(sh);
161 		if (sp != null)
162 			s.setDrawPaint(sp);
163 		if (st != null)
164 			s.setDrawStroke(st);
165 		s.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON);
166 		if (st != null && sp != null)
167 			s.setMode(Mode.STROKE);
168 		else if (sp != null)
169 			s.setMode(Mode.FILL);
170 		return s;
171 	}
172 
173 	/** adjust position + rotation */
174 	private static void syncBroomM2V(final Point2D b, final Affine scene) {
175 		if (b == null)
176 			return;
177 		final AffineTransform t = scene.getAffine();
178 		t.setToIdentity();
179 		t.translate(b.getX(), b.getY());
180 		MathVec.rotate(t, b.getX(), b.getY() - IceSize.FAR_HACK_2_TEE);
181 		MathVec.rotate(t, 0, 1);
182 		scene.setAffine(t);
183 	}
184 
185 	private static void syncHandleM2V(final boolean outTurn, final Affine handle) {
186 		final AffineTransform t = handle.getAffine();
187 		t.setToIdentity();
188 		if (outTurn)
189 			t.scale(-1, 1);
190 		handle.setAffine(t);
191 	}
192 
193 	private static void syncIndexM2V(final int i16, final SGShape pie) {
194 		if (pie != null)
195 			pie.setFillPaint(RockSet.isDark(i16) ? Color.RED : Color.YELLOW);
196 	}
197 
198 	private ChangeManager changer;
199 	private Affine dc2wc;
200 	private final Affine handle;
201 	private BroomPromptModel model;
202 	private final SGShape pie;
203 	private final Affine scene;
204 	private final SGShape sli;
205 	private final Affine slider;
206 	private final float stickLength;
207 	private final Point2D tmp = new Point2D.Double();
208 
209 	public BroomPromptScenario() {
210 		// create the scene
211 		final boolean stickUp = false;
212 		final boolean bothSides = true;
213 		final int pieAngle = 150;
214 		final Color sp = Color.BLACK;
215 		final Color bgc = new Color(1, 1, 1, 0.5f);
216 		final Stroke fine = new BasicStroke(0.01f, BasicStroke.CAP_BUTT,
217 				BasicStroke.JOIN_MITER);
218 		final Stroke bold = new BasicStroke(0.03f, BasicStroke.CAP_ROUND,
219 				BasicStroke.JOIN_MITER);
220 		// final Font fo = new Font("SansSerif", Font.BOLD, 1);
221 		final float halo = RockProps.DEFAULT.getRadius();
222 		final float outer = 0.8f * RockProps.DEFAULT.getRadius();
223 		stickLength = (stickUp ? 1 : -1) * 5 * outer;
224 		final float inner = 0.5F * outer;
225 
226 		final SGGroup me = new SGGroup();
227 		// the transparent background
228 		{
229 			final SGShape bg = node(new Arc2D.Float(-halo, -halo, 2 * halo,
230 					2 * halo, 0, 360, Arc2D.OPEN), null, null, scale0);
231 			bg.setFillPaint(bgc);
232 			bg.addMouseListener(new MoveHandler());
233 			bg.setMouseBlocker(true);
234 			bg.setCursor(moveC);
235 			me.add(bg);
236 		}
237 		// the cross-hair and stick
238 		{
239 			final int off = 90;
240 			final int pieOff = 180;
241 			final int arrowLengthDegrees = 7;
242 			// colored pie:
243 			pie = node(new Arc2D.Float(-outer, -outer, 2 * outer, 2 * outer,
244 					off - pieOff, pieAngle, Arc2D.PIE), null, null, scale0);
245 			me.add(pie);
246 			// inner circle:
247 			me.add(node(new Arc2D.Float(-inner, -inner, 2 * inner, 2 * inner,
248 					off, pieOff + pieAngle, Arc2D.OPEN), fine, sp, scale50));
249 			// outer circle:
250 			me.add(node(new Arc2D.Float(-outer, -outer, 2 * outer, 2 * outer,
251 					off, pieOff + pieAngle - (14 + arrowLengthDegrees),
252 					Arc2D.OPEN), fine, sp, scale50));
253 			// Semantic zooming: me.add(node(new Arc2D.Float(-outer, -outer, 2 *
254 			// outer, 2 * outer,
255 			// off, pieOff + pieAngle, Arc2D.OPEN), fine, sp, -scale50));
256 			final double ar = Math.PI * (off + pieAngle) / 180.0;
257 			// radius
258 			// if (pieAngle % 90 != 0)
259 			me.add(node(new Line2D.Double(0, 0, -outer * Math.cos(ar), outer
260 					* Math.sin(ar)), bold, sp, scale0));
261 
262 			// arrow:
263 			final float f = outer / 10;
264 			final SGShape s = node(IceShapes
265 					.createArrowHead(f, 3 * f, 0.5f * f), null, null, scale50);
266 			s.setFillPaint(sp);
267 			final double a = Math.PI * (off + pieAngle - arrowLengthDegrees)
268 					/ 180.0;
269 			final AffineTransform a_ = new AffineTransform();
270 			a_.translate(-outer * Math.cos(a), outer * Math.sin(a));
271 			a_.rotate(Math.PI
272 					* (90 - (off + pieAngle) + 8 + arrowLengthDegrees) / 180.0);
273 			final Affine s_ = SGTransform.createAffine(a_, s);
274 			me.add(s_);
275 		}
276 		{ // y-axis:
277 			me.add(node(new Line2D.Float(0, -Math.signum(stickLength) * halo,
278 					0, stickLength), fine, sp, scale0));
279 			// x-axis:
280 			me.add(node(new Line2D.Float(-halo, 0, halo, 0), fine, sp, scale0));
281 		}
282 		{ // slider
283 			sli = new SGShape();
284 			sli.setShape(IceShapes.createSlider(0.4f * outer, bothSides));
285 			// sli.setFillPaint(sp);
286 			sli.setDrawStroke(fine);
287 			sli.setDrawPaint(sp);
288 			sli.setMode(Mode.STROKE_FILL);
289 			sli.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON);
290 			me.add(slider = SGTransform
291 					.createAffine(new AffineTransform(), sli));
292 			slider.setCursor(moveC);
293 			slider.addMouseListener(new SpeedHandler());
294 			slider.setMouseBlocker(true);
295 		}
296 		handle = SGTransform.createAffine(new AffineTransform(), me);
297 		scene = SGTransform.createAffine(new AffineTransform(), handle);
298 		scene.setVisible(false);
299 	}
300 
301 	public ChangeManager getChanger() {
302 		return ChangeManager.getTrivial(changer);
303 	}
304 
305 	public Affine getDc2Wc() {
306 		return dc2wc;
307 	}
308 
309 	public BroomPromptModel getModel() {
310 		return model;
311 	}
312 
313 	public SGNode getScene() {
314 		return scene;
315 	}
316 
317 	public void propertyChange(final PropertyChangeEvent evt) {
318 		if (evt.getSource() == model) {
319 			if ("idx16".equals(evt.getPropertyName()))
320 				syncIndexM2V((Integer) evt.getNewValue(), pie);
321 			else if ("outTurn".equals(evt.getPropertyName()))
322 				syncHandleM2V((Boolean) evt.getNewValue(), handle);
323 			else if ("broom".equals(evt.getPropertyName()))
324 				syncBroomM2V((Point2D) evt.getNewValue(), scene);
325 			else if ("splitTimeMillis".equals(evt.getPropertyName())) {
326 				final BoundedRangeModel os = (BoundedRangeModel) evt
327 						.getOldValue();
328 				if (os != null)
329 					os.removeChangeListener(this);
330 				final BoundedRangeModel ns = (BoundedRangeModel) evt
331 						.getNewValue();
332 				if (ns != null)
333 					ns.addChangeListener(this);
334 				syncSpeedM2V(ns);
335 			} else
336 				log.info(evt.getPropertyName() + " " + evt.getSource());
337 		} else
338 			log.warn("Unconsumed event from " + evt.getSource());
339 	}
340 
341 	public void setChanger(final ChangeManager changer) {
342 		final ChangeManager old = this.changer;
343 		if (old == changer)
344 			return;
345 		this.changer = changer;
346 		// firePropertyChange("changer", old, this.changer);
347 	}
348 
349 	public void setDc2wc(final Affine dc2wc) {
350 		this.dc2wc = dc2wc;
351 	}
352 
353 	public void setModel(final BroomPromptModel model) {
354 		if (this.model == model)
355 			return;
356 		if (this.model != null) {
357 			this.model.removePropertyChangeListener(this);
358 			if (this.model.getSplitTimeMillis() != null)
359 				this.model.getSplitTimeMillis().removeChangeListener(this);
360 		}
361 		this.model = model;
362 		scene
363 				.setVisible(true || this.model != null
364 						&& model.getBroom() != null);
365 		if (this.model != null) {
366 			// setBroom(this.model.getBroom());
367 			// setIdx16(this.model.getIdx16());
368 			// setOutTurn(this.model.getOutTurn());
369 			// setSlider(this.model.getSplitTimeMillis());
370 			this.model.addPropertyChangeListener(this);
371 			if (this.model.getSplitTimeMillis() != null)
372 				this.model.getSplitTimeMillis().addChangeListener(this);
373 		}
374 	}
375 
376 	public void stateChanged(final ChangeEvent e) {
377 		if (e.getSource() instanceof BoundedRangeModel)
378 			syncSpeedM2V((BoundedRangeModel) e.getSource());
379 		else
380 			log.warn("Unconsumed event from " + e.getSource());
381 	}
382 
383 	private void syncSpeedM2V(final BoundedRangeModel r) {
384 		// log.info(r.getValue() + "/" + r.getMaximum());
385 		sli.setFillPaint(IceShapes.sliderColor(r));
386 		final AffineTransform t = slider.getAffine();
387 		t.setToTranslation(0, stickLength * IceShapes.value2ratio(r));
388 		slider.setAffine(t);
389 	}
390 }