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;
21  
22  import java.awt.event.ActionEvent;
23  import java.awt.event.ActionListener;
24  import java.awt.event.FocusEvent;
25  import java.awt.event.FocusListener;
26  import java.awt.event.ItemEvent;
27  import java.awt.event.ItemListener;
28  import java.awt.geom.Point2D;
29  import java.beans.PropertyChangeEvent;
30  import java.beans.PropertyChangeListener;
31  
32  import javax.swing.BorderFactory;
33  import javax.swing.BoundedRangeModel;
34  import javax.swing.Box;
35  import javax.swing.BoxLayout;
36  import javax.swing.ButtonGroup;
37  import javax.swing.JComboBox;
38  import javax.swing.JComponent;
39  import javax.swing.JPanel;
40  import javax.swing.JRadioButton;
41  import javax.swing.JSpinner;
42  import javax.swing.SpinnerNumberModel;
43  import javax.swing.event.ChangeEvent;
44  import javax.swing.event.ChangeListener;
45  
46  import org.apache.commons.logging.Log;
47  import org.jcurl.core.api.ComputedTrajectorySet;
48  import org.jcurl.core.api.IceSize;
49  import org.jcurl.core.api.RockSet;
50  import org.jcurl.core.api.Unit;
51  import org.jcurl.core.log.JCLoggerFactory;
52  import org.jcurl.core.ui.BroomPromptModel;
53  import org.jcurl.core.ui.ChangeManager;
54  import org.jcurl.core.ui.JSpinnerBoundedRange;
55  import org.jcurl.core.ui.TrajectoryBroomPromptWrapper;
56  import org.jcurl.core.ui.BroomPromptModel.HandleMemento;
57  import org.jcurl.core.ui.BroomPromptModel.IndexMemento;
58  import org.jcurl.core.ui.BroomPromptModel.SplitMemento;
59  import org.jcurl.core.ui.BroomPromptModel.XYMemento;
60  
61  /**
62   * A Swing-based bean to control a {@link BroomPromptModel}. TODO x and y!
63   * <p>
64   * As Swing views are usually tightly coupled to the underlying datamodels
65   * (which holds at least for the {@link JSpinner}), I'm not quite sure how to
66   * pipe the changes through
67   * {@link ChangeManager#temporary(org.jcurl.core.ui.Memento)}. So I only use
68   * {@link ChangeManager#undoable(org.jcurl.core.ui.Memento, org.jcurl.core.ui.Memento)}
69   * to feed the undo manager (which results in duplicate updates to the
70   * underlying datamodels).
71   * </p>
72   * <p>
73   * Sp once more the coupling between {@link BroomPromptModel} and
74   * {@link ComputedTrajectorySet} is problematic. See
75   * {@link TrajectoryBroomPromptWrapper}.
76   * </p>
77   * 
78   * @author <a href="mailto:JCurl@mro.name">M. Rohrmoser </a>
79   * @version $Id$
80   */
81  public class BroomPromptSwingBean extends JComponent implements HasChanger,
82  		ItemListener, FocusListener, ChangeListener, ActionListener,
83  		PropertyChangeListener {
84  
85  	private static final Log log = JCLoggerFactory
86  			.getLogger(BroomPromptSwingBean.class);
87  	private static final long serialVersionUID = -3512129363499720146L;
88  	private static final boolean UseJSpinnerBoundedRange = true;
89  	private BroomPromptModel broom;
90  	private ChangeManager changer;
91  	private final JRadioButton dark, light;
92  	private final JComponent dt;
93  	private SplitMemento first = null;
94  	private transient XYMemento firstXY = null;
95  	private final JRadioButton in, out;
96  	private final JComboBox rock;
97  	private final JSpinner split;
98  	private final JSpinnerBoundedRange split2;
99  	private final JSpinnerNumberUnit x;
100 
101 	private final JSpinnerNumberUnit y;
102 
103 	public BroomPromptSwingBean() {
104 		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
105 
106 		final Box b = Box.createVerticalBox();
107 		{
108 			final JPanel tb = new JPanel();
109 			tb.setLayout(new BoxLayout(tb, BoxLayout.X_AXIS));
110 			tb.setBorder(BorderFactory.createTitledBorder("Active"));
111 			tb
112 					.add(rock = new JComboBox(new Object[] { 1, 2, 3, 4, 5, 6,
113 							7, 8 }));
114 			rock.setPrototypeDisplayValue(8);
115 			rock.addItemListener(this);
116 			dark = new JRadioButton("dark");
117 			dark.addActionListener(this);
118 			light = new JRadioButton("light");
119 			light.addActionListener(this);
120 			final ButtonGroup bg = new ButtonGroup();
121 			bg.add(dark);
122 			bg.add(light);
123 			tb.add(dark);
124 			tb.add(light);
125 			b.add(tb);
126 		}
127 		{
128 			final JPanel tb = new JPanel();
129 			tb.setLayout(new BoxLayout(tb, BoxLayout.X_AXIS));
130 			tb.setBorder(BorderFactory.createTitledBorder("Handle"));
131 			in = new JRadioButton("In Turn");
132 			in.addActionListener(this);
133 			out = new JRadioButton("Out Turn");
134 			out.addActionListener(this);
135 			final ButtonGroup bg = new ButtonGroup();
136 			bg.add(in);
137 			bg.add(out);
138 			tb.add(out);
139 			tb.add(in);
140 			tb.add(Box.createHorizontalGlue());
141 			b.add(tb);
142 		}
143 		{
144 			final JPanel tb = new JPanel();
145 			tb.setLayout(new BoxLayout(tb, BoxLayout.X_AXIS));
146 			tb.setBorder(BorderFactory.createTitledBorder("Split Time"));
147 
148 			if (UseJSpinnerBoundedRange) {
149 				split2 = new JSpinnerBoundedRange();
150 				split2.addFocusListener(this);
151 				split = null;
152 			} else {
153 				split2 = null;
154 				split = new JSpinner();
155 				// log.info(split.getEditor().getClass().getName());
156 				split.addFocusListener(this);
157 				final JSpinner.NumberEditor ed = (JSpinner.NumberEditor) split
158 						.getEditor();
159 				ed.addFocusListener(this);
160 				ed.getTextField().addFocusListener(this);
161 			}
162 
163 			tb.add(split2);
164 
165 			tb.add(dt = new JComboBox(new Object[] { "1/1000 sec", "1/100 sec",
166 					"1/10 sec", "sec" }));
167 			tb.add(Box.createHorizontalGlue());
168 			b.add(tb);
169 			dt.setEnabled(false);
170 		}
171 		{
172 			final JPanel p = new JPanel();
173 			p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
174 			p.setBorder(BorderFactory.createTitledBorder("Broom Position"));
175 			{
176 				x = new JSpinnerNumberUnit();
177 				x.setLabel("x: ");
178 				x.setBase(Unit.METER);
179 				x.setChoose(Unit.FOOT, Unit.INCH, Unit.CENTIMETER, Unit.METER);
180 				x.setModel(new SpinnerNumberModel(0.0, -IceSize.SIDE_2_CENTER,
181 						IceSize.SIDE_2_CENTER, 0.1));
182 				x.addChangeListener(this);
183 				x.addPropertyChangeListener(this);
184 				p.add(x);
185 			}
186 			{
187 				y = new JSpinnerNumberUnit();
188 				y.setLabel("y: ");
189 				y.setBase(Unit.METER);
190 				y.setChoose(Unit.FOOT, Unit.INCH, Unit.CENTIMETER, Unit.METER);
191 				y.setModel(new SpinnerNumberModel(0.0, -IceSize.BACK_2_TEE,
192 						IceSize.HOG_2_TEE, 0.1));
193 				y.addChangeListener(this);
194 				y.addPropertyChangeListener(this);
195 				p.add(y);
196 			}
197 			b.add(p);
198 		}
199 		this.add(b);
200 	}
201 
202 	public void actionPerformed(final ActionEvent e) {
203 		if (log.isDebugEnabled())
204 			log.debug(e.getSource());
205 		if (e.getSource() == dark || e.getSource() == light) {
206 			updateIndex();
207 			return;
208 		}
209 		if (e.getSource() == in || e.getSource() == out) {
210 			updateHandle();
211 			return;
212 		}
213 		log.warn("Unprocessed event from " + e.getSource());
214 	}
215 
216 	private int findIndex() {
217 		final int idx8 = rock.getSelectedIndex();
218 		if (idx8 < 0)
219 			return -1;
220 		if (dark.isSelected())
221 			return RockSet.toIdx16(true, idx8);
222 		if (light.isSelected())
223 			return RockSet.toIdx16(false, idx8);
224 		return -1;
225 	}
226 
227 	public void focusGained(final FocusEvent e) {
228 		log.debug(e.getSource());
229 		if (e.getSource() == split2) {
230 			if (broom == null || broom.getSplitTimeMillis() == null)
231 				return;
232 			first = new SplitMemento(broom, broom.getSplitTimeMillis()
233 					.getValue());
234 		} else
235 			log.warn("Unprocessed event from " + e.getSource());
236 	}
237 
238 	public void focusLost(final FocusEvent e) {
239 		if (e.getSource() == split2) {
240 			if (broom == null || broom.getSplitTimeMillis() == null)
241 				return;
242 			getChanger().undoable(
243 					first,
244 					new SplitMemento(broom, broom.getSplitTimeMillis()
245 							.getValue()));
246 			first = null;
247 		} else
248 			log.warn("Unprocessed event from " + e.getSource());
249 	}
250 
251 	public BroomPromptModel getBroom() {
252 		return broom;
253 	}
254 
255 	public ChangeManager getChanger() {
256 		return ChangeManager.getTrivial(changer);
257 	}
258 
259 	public void itemStateChanged(final ItemEvent e) {
260 		if (log.isDebugEnabled())
261 			log.debug(e.getSource());
262 		if (e.getSource() == rock) {
263 			updateIndex();
264 			return;
265 		}
266 		log.warn("Unprocessed event from " + e.getSource());
267 	}
268 
269 	public void propertyChange(final PropertyChangeEvent evt) {
270 		if (log.isDebugEnabled())
271 			log.debug(evt.getSource());
272 		if (evt.getSource() == broom) {
273 			if ("idx16".equals(evt.getPropertyName()))
274 				syncM2V(broom == null ? -1 : broom.getIdx16());
275 			else if ("outTurn".equals(evt.getPropertyName()))
276 				syncM2V(broom == null ? null : broom.getOutTurn());
277 			else if ("splitTimeMillis".equals(evt.getPropertyName()))
278 				syncM2V(broom == null ? null : broom.getSplitTimeMillis());
279 			else if ("broom".equals(evt.getPropertyName()))
280 				syncM2V(broom == null ? null : broom.getBroom());
281 		} else if (evt.getSource() == x || evt.getSource() == y) {
282 			if ("valueIsAdjusting".equals(evt.getPropertyName()))
283 				if (Boolean.TRUE.equals(evt.getNewValue()))
284 					firstXY = new XYMemento(broom, broom.getBroom());
285 				else {
286 					if (Boolean.FALSE.equals(evt.getNewValue()))
287 						getChanger()
288 								.undoable(
289 										firstXY,
290 										new XYMemento(broom, x.getValue(), y
291 												.getValue()));
292 					firstXY = null;
293 				}
294 		} else
295 			log.warn("Unprocessed event from " + evt.getSource());
296 	}
297 
298 	public void setBroom(final BroomPromptModel broom) {
299 		log.debug(broom);
300 		if (this.broom == broom)
301 			return;
302 		if (this.broom != null)
303 			this.broom.removePropertyChangeListener(this);
304 		this.broom = broom;
305 		syncM2V(this.broom == null ? -1 : this.broom.getIdx16());
306 		syncM2V(this.broom == null ? null : this.broom.getOutTurn());
307 		syncM2V(this.broom == null ? null : this.broom.getSplitTimeMillis());
308 		syncM2V(this.broom == null ? null : this.broom.getBroom());
309 		if (this.broom != null)
310 			this.broom.addPropertyChangeListener(this);
311 	}
312 
313 	public void setChanger(final ChangeManager changer) {
314 		final ChangeManager old = this.changer;
315 		if (old == changer)
316 			return;
317 		firePropertyChange("changer", old, this.changer = changer);
318 	}
319 
320 	@Override
321 	public void setEnabled(boolean enabled) {
322 		super.setEnabled(enabled);
323 		rock.setEnabled(enabled);
324 		dark.setEnabled(enabled);
325 		light.setEnabled(enabled);
326 
327 		in.setEnabled(enabled);
328 		out.setEnabled(enabled);
329 
330 		if (UseJSpinnerBoundedRange)
331 			split2.setEnabled(enabled);
332 		else
333 			split.setEnabled(enabled);
334 
335 		x.setEnabled(enabled);
336 		y.setEnabled(enabled);
337 
338 		enabled = false;
339 		dt.setEnabled(enabled);
340 	}
341 
342 	public void stateChanged(final ChangeEvent e) {
343 		if (log.isDebugEnabled())
344 			log.debug(e.getSource());
345 		if (e.getSource() == x) {
346 			final double nx = x.getValue();
347 			if (broom.getBroom().getX() != nx)
348 				getChanger().temporary(
349 						new XYMemento(broom, nx, broom.getBroom().getY()));
350 		} else if (e.getSource() == y) {
351 			final double ny = y.getValue();
352 			if (broom.getBroom().getY() != ny)
353 				getChanger().temporary(
354 						new XYMemento(broom, broom.getBroom().getX(), ny));
355 		} else
356 			log.warn("Unprocessed event from " + e.getSource());
357 	}
358 
359 	private void syncM2V(final Boolean handle) {
360 		if (log.isDebugEnabled())
361 			log.debug("handle="
362 					+ (handle == null ? "-" : handle.booleanValue() ? "out"
363 							: "in"));
364 		if (handle == null) {
365 			in.setEnabled(false);
366 			out.setEnabled(false);
367 			in.setSelected(false);
368 			out.setSelected(false);
369 		} else {
370 			in.setEnabled(true);
371 			out.setEnabled(true);
372 			if (handle.booleanValue())
373 				out.setSelected(true);
374 			else
375 				in.setSelected(true);
376 		}
377 	}
378 
379 	private void syncM2V(final BoundedRangeModel s) {
380 		if (s != null) {
381 			if (UseJSpinnerBoundedRange) {
382 				split2.setBRM(s);
383 				split2.setEnabled(true);
384 			} else {
385 				// split.setModel(new
386 				// JSpinnerBoundedRange.SpinnerModelWrapper(s));
387 				split.setEnabled(true);
388 				// add focus event listener
389 				// http://forum.java.sun.com/thread.jspa?threadID=409748&forumID=57
390 				final JSpinner.DefaultEditor ed = (JSpinner.DefaultEditor) split
391 						.getEditor();
392 				ed.getTextField().addFocusListener(this);
393 			}
394 		} else // split.setModel(null);
395 		if (UseJSpinnerBoundedRange)
396 			split2.setEnabled(false);
397 		else
398 			split.setEnabled(false);
399 	}
400 
401 	private void syncM2V(final int idx16) {
402 		if (log.isDebugEnabled())
403 			log.debug("idx16=" + idx16);
404 		if (idx16 < 0) {
405 			dark.setSelected(false);
406 			light.setSelected(false);
407 			rock.setSelectedIndex(-1);
408 			dark.setEnabled(false);
409 			light.setEnabled(false);
410 			rock.setEnabled(false);
411 		} else {
412 			dark.setEnabled(true);
413 			light.setEnabled(true);
414 			rock.setEnabled(true);
415 			if (RockSet.isDark(idx16))
416 				dark.setSelected(true);
417 			else
418 				light.setSelected(true);
419 			rock.setSelectedIndex(RockSet.toIdx8(idx16));
420 		}
421 	}
422 
423 	private void syncM2V(final Point2D s) {
424 		if (s == null) {
425 			x.setValue(0);
426 			y.setValue(0);
427 		} else {
428 			x.setValue(s.getX());
429 			y.setValue(s.getY());
430 		}
431 		x.setEnabled(s != null);
432 		y.setEnabled(s != null);
433 	}
434 
435 	private void updateHandle() {
436 		if (broom == null)
437 			return;
438 		final HandleMemento pre = new HandleMemento(broom, broom.getOutTurn());
439 		final HandleMemento post;
440 		if (in.isSelected())
441 			post = new HandleMemento(broom, false);
442 		else if (out.isSelected())
443 			post = new HandleMemento(broom, true);
444 		else
445 			post = null;
446 		getChanger().undoable(pre, post);
447 	}
448 
449 	private void updateIndex() {
450 		if (broom == null)
451 			return;
452 		if (broom == null)
453 			return;
454 		final IndexMemento pre = new IndexMemento(broom, broom.getIdx16());
455 		final IndexMemento post = new IndexMemento(broom, findIndex());
456 		getChanger().undoable(pre, post);
457 	}
458 }