1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.jcurl.demo.tactics;
21
22 import java.awt.BorderLayout;
23 import java.awt.Container;
24 import java.awt.Cursor;
25 import java.awt.Dimension;
26 import java.awt.Frame;
27 import java.awt.Graphics;
28 import java.awt.GridBagConstraints;
29 import java.awt.GridBagLayout;
30 import java.awt.Image;
31 import java.awt.Insets;
32 import java.awt.Toolkit;
33 import java.awt.geom.Point2D;
34 import java.awt.geom.Rectangle2D;
35 import java.awt.geom.RectangularShape;
36 import java.awt.image.BufferedImage;
37 import java.io.File;
38 import java.io.IOException;
39 import java.net.MalformedURLException;
40 import java.net.URISyntaxException;
41 import java.net.URL;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.EventObject;
45 import java.util.List;
46 import java.util.Locale;
47
48 import javax.imageio.ImageIO;
49 import javax.swing.JButton;
50 import javax.swing.JComponent;
51 import javax.swing.JDialog;
52 import javax.swing.JFileChooser;
53 import javax.swing.JLabel;
54 import javax.swing.JMenu;
55 import javax.swing.JMenuBar;
56 import javax.swing.JMenuItem;
57 import javax.swing.JOptionPane;
58 import javax.swing.JPanel;
59 import javax.swing.JSeparator;
60 import javax.swing.JTabbedPane;
61 import javax.swing.JTextField;
62 import javax.swing.JToolBar;
63 import javax.swing.SwingConstants;
64 import javax.swing.border.EmptyBorder;
65 import javax.swing.event.ChangeEvent;
66 import javax.swing.event.ChangeListener;
67 import javax.swing.event.UndoableEditEvent;
68 import javax.swing.event.UndoableEditListener;
69 import javax.swing.filechooser.FileFilter;
70 import javax.swing.undo.CompoundEdit;
71
72 import org.apache.commons.logging.Log;
73 import org.jcurl.batik.BatikWrapper;
74 import org.jcurl.core.api.ComputedTrajectorySet;
75 import org.jcurl.core.api.IceSize;
76 import org.jcurl.core.api.RockProps;
77 import org.jcurl.core.api.RockSet;
78 import org.jcurl.core.api.RockSetUtils;
79 import org.jcurl.core.api.TrajectorySet;
80 import org.jcurl.core.api.Unit;
81 import org.jcurl.core.api.RockType.Pos;
82 import org.jcurl.core.api.RockType.Vel;
83 import org.jcurl.core.helpers.BatikButler;
84 import org.jcurl.core.io.IONode;
85 import org.jcurl.core.io.IOTrajectories;
86 import org.jcurl.core.io.JCurlSerializer;
87 import org.jcurl.core.io.JDKSerializer;
88 import org.jcurl.core.log.JCLoggerFactory;
89 import org.jcurl.core.ui.BroomPromptModel;
90 import org.jcurl.core.ui.ChangeManager;
91 import org.jcurl.core.ui.FileNameExtensionFilter;
92 import org.jcurl.core.ui.PosMemento;
93 import org.jcurl.core.ui.SuspendMemento;
94 import org.jcurl.core.ui.UndoableMemento;
95 import org.jcurl.core.ui.BroomPromptModel.HandleMemento;
96 import org.jcurl.core.ui.BroomPromptModel.IndexMemento;
97 import org.jcurl.core.ui.BroomPromptModel.SplitMemento;
98 import org.jcurl.core.ui.BroomPromptModel.XYMemento;
99 import org.jdesktop.application.Action;
100 import org.jdesktop.application.Application;
101 import org.jdesktop.application.ApplicationAction;
102 import org.jdesktop.application.ApplicationContext;
103 import org.jdesktop.application.ResourceMap;
104 import org.jdesktop.application.SingleFrameApplication;
105 import org.jdesktop.application.Task;
106 import org.jdesktop.application.Task.BlockingScope;
107
108
109
110
111
112
113
114
115
116 public class JCurlShotPlanner extends SingleFrameApplication implements
117 UndoableEditListener {
118 private static class ChangeListenerManager implements ChangeListener {
119 private final JCurlShotPlanner host;
120
121 public ChangeListenerManager(final JCurlShotPlanner host) {
122 this.host = host;
123 }
124
125 public void deregister(final ComputedTrajectorySet cts) {
126 if (cts == null)
127 return;
128 cts.getInitialPos().removeRockListener(this);
129 cts.getInitialVel().removeRockListener(this);
130
131
132 }
133
134 public void register(final ComputedTrajectorySet cts) {
135 if (cts == null)
136 return;
137 cts.getInitialPos().addRockListener(this);
138 cts.getInitialVel().addRockListener(this);
139
140
141 }
142
143 public void stateChanged(final ChangeEvent e) {
144 host.setModified(true);
145 }
146 }
147
148 static class GuiUtil {
149
150 private static final Insets zeroInsets = new Insets(0, 0, 0, 0);
151
152 private final ApplicationContext act;;
153
154 public GuiUtil(final ApplicationContext act) {
155 this.act = act;
156 }
157
158
159
160
161
162
163
164
165
166
167
168
169
170 private JDialog createAboutBox(final Frame owner) {
171 final JPanel panel = new JPanel(new GridBagLayout());
172 panel.setBorder(new EmptyBorder(0, 28, 16, 28));
173
174 final JLabel titleLabel = new JLabel();
175 titleLabel.setName("aboutTitleLabel");
176 final GridBagConstraints c = new GridBagConstraints();
177 initGridBagConstraints(c);
178 c.anchor = GridBagConstraints.WEST;
179 c.gridwidth = GridBagConstraints.REMAINDER;
180 c.fill = GridBagConstraints.HORIZONTAL;
181 c.ipady = 32;
182 c.weightx = 1.0;
183 panel.add(titleLabel, c);
184 final String[] fields = { "description", "version", "vendor",
185 "home" };
186 for (final String field : fields) {
187 final JLabel label = new JLabel();
188 label.setName(field + "Label");
189 initGridBagConstraints(c);
190
191 c.anchor = GridBagConstraints.EAST;
192 panel.add(label, c);
193 initGridBagConstraints(c);
194 c.weightx = 1.0;
195 c.gridwidth = GridBagConstraints.REMAINDER;
196 c.fill = GridBagConstraints.HORIZONTAL;
197 final JTextField textField = new JTextField();
198 textField.setName(field + "TextField");
199 textField.setEditable(false);
200 textField.setBorder(null);
201 panel.add(textField, c);
202 }
203 final JButton closeAboutButton = new JButton();
204 closeAboutButton.setAction(findAction("closeAboutBox"));
205 initGridBagConstraints(c);
206 c.anchor = GridBagConstraints.EAST;
207 c.gridx = 1;
208 panel.add(closeAboutButton, c);
209 final JDialog dialog = new JDialog(owner);
210 dialog.setName("aboutDialog");
211 dialog.add(panel, BorderLayout.CENTER);
212 return dialog;
213 }
214
215 private JFileChooser createFileChooser(final File base,
216 final String resourceName, final FileFilter filter) {
217 final JFileChooser fc = new JFileChooser(base);
218 fc.setName(resourceName);
219 fc.setMultiSelectionEnabled(false);
220 fc.setAcceptAllFileFilterUsed(true);
221 fc.setFileFilter(filter);
222 getContext().getResourceMap().injectComponents(fc);
223 return fc;
224 }
225
226 private FileNameExtensionFilter createFileFilter(
227 final String resourceName, final String... extensions) {
228 final ResourceMap appResourceMap = getContext().getResourceMap();
229 final String key = resourceName + ".description";
230 final String desc = appResourceMap.getString(key);
231 return new FileNameExtensionFilter(desc == null ? key : desc,
232 extensions);
233 }
234
235 private JMenu createMenu(final String menuName,
236 final String[] actionNames) {
237 final JMenu menu = new JMenu();
238 menu.setName(menuName);
239 for (final String actionName : actionNames)
240 if (actionName.equals("---"))
241 menu.add(new JSeparator());
242 else {
243 final JMenuItem menuItem = new JMenuItem();
244 menuItem.setAction(findAction(actionName));
245 menuItem.setIcon(null);
246 menu.add(menuItem);
247 }
248 return menu;
249 }
250
251 private File ensureSuffix(final File dst,
252 final FileNameExtensionFilter pat) {
253 if (pat.accept(dst))
254 return dst;
255 return new File(dst.getAbsoluteFile() + "."
256 + pat.getExtensions()[0]);
257 }
258
259 private javax.swing.Action findAction(final String actionName) {
260 return getContext().getActionMap().get(actionName);
261 };
262
263 public ApplicationContext getContext() {
264 return act;
265 };
266
267 private void initGridBagConstraints(final GridBagConstraints c) {
268 c.anchor = GridBagConstraints.CENTER;
269 c.fill = GridBagConstraints.NONE;
270 c.gridwidth = 1;
271 c.gridheight = 1;
272 c.gridx = GridBagConstraints.RELATIVE;
273 c.gridy = GridBagConstraints.RELATIVE;
274 c.insets = zeroInsets;
275 c.ipadx = 4;
276 c.ipady = 4;
277 c.weightx = 0.0;
278 c.weighty = 0.0;
279 };
280 }
281
282 abstract static class WaitCursorTask<T, V> extends Task<T, V> {
283 private final SingleFrameApplication app;
284
285 public WaitCursorTask(final SingleFrameApplication app) {
286 super(app);
287 this.app = app;
288 }
289
290 protected abstract T doCursor() throws Exception;
291
292 @Override
293 protected T doInBackground() throws Exception {
294 final Cursor cu = app.getMainFrame().getCursor();
295 try {
296 app.getMainFrame().setCursor(waitc);
297 Thread.yield();
298 return doCursor();
299 } finally {
300 app.getMainFrame().setCursor(cu);
301 Thread.yield();
302 }
303 }
304 }
305
306 public static class ZoomHelper {
307
308
309
310
311 public static final Rectangle2D ActivePlus;
312
313 public static final Rectangle2D CompletePlus;
314
315 public static final Rectangle2D HousePlus;
316
317 public static final Rectangle2D TwelvePlus;
318 static {
319 final double r2 = 2 * RockProps.DEFAULT.getRadius();
320 final double x = IceSize.SIDE_2_CENTER + r2;
321 HousePlus = new Rectangle2D.Double(-x, -(IceSize.HOG_2_TEE + r2),
322 2 * x, IceSize.HOG_2_TEE + IceSize.BACK_2_TEE + 3 * r2 + 2
323 * r2);
324 final double c12 = r2 + Unit.f2m(6.0);
325 TwelvePlus = new Rectangle2D.Double(-c12, -c12, 2 * c12, 2 * c12);
326 ActivePlus = new Rectangle2D.Double(-x, -(IceSize.HOG_2_HOG
327 + IceSize.HOG_2_TEE + r2), 2 * x, IceSize.HOG_2_HOG
328 + IceSize.HOG_2_TEE + IceSize.BACK_2_TEE + 3 * r2 + 2 * r2);
329 CompletePlus = new Rectangle2D.Double(-x, -(IceSize.HOG_2_TEE
330 + IceSize.HOG_2_HOG + IceSize.HACK_2_HOG + r2), 2 * x,
331 IceSize.HOG_2_HOG + 2 * IceSize.HACK_2_HOG);
332 }
333
334 private void pan(final Zoomable dst, final double rx, final double ry,
335 final int dt) {
336 if (dst == null)
337 return;
338 final RectangularShape src = dst.getZoom();
339 zoom(dst, new Rectangle2D.Double(src.getX() + src.getWidth() * rx,
340 src.getY() + src.getHeight() * ry, src.getWidth(), src
341 .getHeight()), dt);
342 }
343
344 private void zoom(final Zoomable dst, final Point2D center,
345 final double ratio, final int dt) {
346 if (dst == null)
347 return;
348 final RectangularShape src = dst.getZoom();
349 final double w = src.getWidth() * ratio;
350 final double h = src.getHeight() * ratio;
351 final double cx, cy;
352 if (center == null) {
353 cx = src.getCenterX();
354 cy = src.getCenterY();
355 } else {
356 cx = center.getX();
357 cy = center.getY();
358 }
359 zoom(dst, new Rectangle2D.Double(cx - w / 2, cy - h / 2, Math
360 .abs(w), Math.abs(h)), dt);
361 }
362
363 private void zoom(final Zoomable dst, final RectangularShape viewport,
364 final int dt) {
365 if (dst == null)
366 return;
367 dst.setZoom(viewport, dt);
368 }
369 }
370
371 private static final BatikButler batik = new BatikButler();
372 private static final double currentTime = 30;
373 private static final int FAST = 200;
374 private static URL initialScene = null;
375 private static final Log log = JCLoggerFactory
376 .getLogger(JCurlShotPlanner.class);
377 private static final int SLOW = 333;
378 private static URL templateScene = null;
379 private static final Cursor waitc = Cursor
380 .getPredefinedCursor(Cursor.WAIT_CURSOR);
381 private static final ZoomHelper zh = new ZoomHelper();
382
383 public static void main(final String[] args) {
384
385 Locale.setDefault(Locale.CANADA);
386 launch(JCurlShotPlanner.class, args);
387 }
388
389 private static void renderPng(final Container src, final File dst)
390 throws IOException {
391 final BufferedImage img = new BufferedImage(src.getWidth(), src
392 .getHeight(), BufferedImage.TYPE_INT_ARGB);
393 final Graphics g = img.getGraphics();
394 try {
395
396 src.paintAll(g);
397 } finally {
398 g.dispose();
399 }
400 ImageIO.write(img, "png", dst);
401 }
402
403 @SuppressWarnings("unchecked")
404 private static CompoundEdit reset(final ComputedTrajectorySet cts,
405 final BroomPromptModel broom, final boolean outPosition) {
406 final RockSet<Pos> ipos = cts.getInitialPos();
407 final RockSet<Vel> ivel = cts.getInitialVel();
408
409 final PosMemento[] pm = new PosMemento[RockSet.ROCKS_PER_SET];
410 for (int i16 = RockSet.ROCKS_PER_SET - 1; i16 >= 0; i16--)
411 pm[i16] = new PosMemento(ipos, i16, ipos.getRock(i16).p());
412 final IndexMemento bi = new IndexMemento(broom, broom.getIdx16());
413 final HandleMemento bh = new HandleMemento(broom, broom.getOutTurn());
414 final XYMemento bxy = new XYMemento(broom, broom.getBroom());
415 final SplitMemento bs = new SplitMemento(broom, broom
416 .getSplitTimeMillis().getValue());
417 final boolean preS = cts.getSuspended();
418 cts.setSuspended(true);
419 try {
420
421 RockSet.allZero(ivel);
422 broom.setIdx16(-1);
423 if (outPosition)
424 RockSetUtils.allOut(ipos);
425 else
426 RockSetUtils.allHome(ipos);
427 broom.setIdx16(1);
428 broom.setBroom(new Point2D.Double(0, 0));
429 broom.getSplitTimeMillis().setValue(3300);
430 } finally {
431 cts.setSuspended(preS);
432 }
433
434 final CompoundEdit ce = new CompoundEdit();
435 ce.addEdit(new UndoableMemento(new SuspendMemento(cts, preS),
436 new SuspendMemento(cts, true)));
437 for (int i16 = RockSet.ROCKS_PER_SET - 1; i16 >= 0; i16--)
438 ce.addEdit(new UndoableMemento(pm[i16], new PosMemento(ipos, i16,
439 ipos.getRock(i16).p())));
440 ce.addEdit(new UndoableMemento(bi, new IndexMemento(broom, broom
441 .getIdx16())));
442 ce.addEdit(new UndoableMemento(bh, new HandleMemento(broom, broom
443 .getOutTurn())));
444 ce.addEdit(new UndoableMemento(bxy, new XYMemento(broom, broom
445 .getBroom())));
446 ce.addEdit(new UndoableMemento(bs, new SplitMemento(broom, broom
447 .getSplitTimeMillis().getValue())));
448 ce.addEdit(new UndoableMemento(new SuspendMemento(cts, true),
449 new SuspendMemento(cts, preS)));
450 ce.end();
451 return ce;
452 }
453
454 private JDialog aboutBox = null;
455 private final BirdPiccoloBean birdPiccolo = new BirdPiccoloBean();
456 private final BroomPromptSwingBean broomSwing = new BroomPromptSwingBean();
457 private boolean canRedo = false;
458 private boolean canUndo = false;
459 private final ChangeManager change = new ChangeManager();
460 private final ChangeListenerManager cm = new ChangeListenerManager(this);
461 private final CurlerSwingBean curlerSwing = new CurlerSwingBean();
462 private URL document;
463 private File file;
464 private final GuiUtil gui = new GuiUtil(getContext());
465 private FileNameExtensionFilter jcxzPat;
466 private boolean modified = false;
467 private FileNameExtensionFilter pngPat;
468 private FileNameExtensionFilter svgPat;
469 private final TrajectoryBean tactics = new TrajectoryPiccoloBean();
470 private final JLabel url = new JLabel();
471
472 private JCurlShotPlanner() {
473 change.addUndoableEditListener(this);
474 tactics.setChanger(change);
475 broomSwing.setChanger(change);
476 curlerSwing.setChanger(change);
477 birdPiccolo.setMaster(tactics);
478
479 url.setName("urlLabel");
480 }
481
482 public boolean askDiscardUnsaved(final javax.swing.Action action) {
483 if (!isModified())
484 return true;
485 final String title, msg;
486 if (true) {
487 final ResourceMap r = getContext().getResourceMap();
488 title = r.getString("discard" + ".Dialog" + ".title", action
489 .getValue(javax.swing.Action.NAME));
490 msg = r.getString("discard" + ".Dialog" + ".message");
491 } else if (action instanceof ApplicationAction) {
492 final ApplicationAction aa = (ApplicationAction) action;
493 final ResourceMap r = getContext().getResourceMap();
494 title = action == null ? null : r.getString(aa.getName()
495 + ".Dialog" + ".title", aa
496 .getValue(javax.swing.Action.NAME));
497 msg = r.getString(aa.getName() + ".Dialog" + ".message");
498 } else {
499 title = null;
500 msg = "Discard unsaved changes?";
501 }
502 return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
503 getMainFrame(), msg, title, JOptionPane.YES_NO_OPTION,
504 JOptionPane.WARNING_MESSAGE);
505 }
506
507 public boolean askOverwrite(final File f) {
508 if (!f.exists())
509 return true;
510 final String title, msg;
511 {
512 final ResourceMap r = getContext().getResourceMap();
513 title = r.getString("overwrite" + ".Dialog" + ".title");
514 msg = r.getString("overwrite" + ".Dialog" + ".message", f
515 .toString());
516 }
517 return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
518 getMainFrame(), msg, title, JOptionPane.YES_NO_OPTION,
519 JOptionPane.WARNING_MESSAGE);
520 };
521
522 @Action
523 public void closeAboutBox() {
524 if (aboutBox == null)
525 return;
526 aboutBox.setVisible(false);
527 aboutBox = null;
528 }
529
530 private JFileChooser createJcxChooser(final File base, final String name) {
531 return gui.createFileChooser(base, name, jcxzPat);
532 };
533
534 private JMenuBar createMenuBar() {
535 final JMenuBar menuBar = new JMenuBar();
536
537 final String[] fileMenuActionNames = {
538 "fileNewDoc", "fileHammy", "---", "fileOpen", "fileOpenURL", "---",
539 "fileReset", "fileSave", "fileSaveAs", "fileSaveCopyAs", "---",
540 "fileExportPng", "fileExportSvg", "---", "quit" };
541 menuBar.add(gui.createMenu("fileMenu", fileMenuActionNames));
542
543 final String[] editMenuActionNames = { "editUndo", "editRedo", "---",
544 "editOut", "editHome", "---", "editProperties", "---",
545 "editPreferences" };
546 menuBar.add(gui.createMenu("editMenu", editMenuActionNames));
547
548 final String[] viewMenuActionNames = { "viewHouse", "view12Foot",
549 "viewComplete", "viewActive", "---", "viewZoomIn",
550 "viewZoomOut", "---", "viewPanNorth", "viewPanSouth",
551 "viewPanEast", "viewPanWest" };
552 menuBar.add(gui.createMenu("viewMenu", viewMenuActionNames));
553
554 final String[] helpMenuActionNames = { "showAboutBox",
555 "helpDumpProperties" };
556 menuBar.add(gui.createMenu("helpMenu", helpMenuActionNames));
557
558 return menuBar;
559 }
560
561 private JFileChooser createPngChooser(final File base, final String name) {
562 return gui.createFileChooser(base, name, pngPat);
563 }
564
565 private JFileChooser createSvgChooser(final File base, final String name) {
566 return gui.createFileChooser(base, name, svgPat);
567 }
568
569 private JComponent createToolBar() {
570 final String[] toolbarActionNames = { "cut", "copy", "paste" };
571 final JToolBar toolBar = new JToolBar();
572 toolBar.setFloatable(false);
573 for (final String actionName : toolbarActionNames) {
574 final JButton button = new JButton();
575 button.setAction(gui.findAction(actionName));
576 button.setFocusable(false);
577 toolBar.add(button);
578 }
579 return toolBar;
580 }
581
582
583 @Action
584 public void editHome() {
585 final ComputedTrajectorySet cts = tactics.getCurves();
586 if (cts == null)
587 return;
588 change.addEdit(reset(cts, tactics.getBroom(), false));
589 }
590
591
592 @Action
593 public void editOut() {
594 final ComputedTrajectorySet cts = tactics.getCurves();
595 if (cts == null)
596 return;
597 change.addEdit(reset(cts, tactics.getBroom(), true));
598 }
599
600
601 @Action(enabledProperty = "alwaysFalse")
602 public void editPreferences() {}
603
604
605 @Action(enabledProperty = "alwaysFalse")
606 public void editProperties() {}
607
608
609 @Action(enabledProperty = "canRedo")
610 public void editRedo() {
611 change.redo();
612 }
613
614
615 @Action(enabledProperty = "canUndo")
616 public void editUndo() {
617 change.undo();
618 }
619
620
621 @Action
622 private void fileClear() {
623 if (!askDiscardUnsaved(gui.findAction("fileClear")))
624 return;
625 try {
626 setDocument(null);
627 } catch (final IOException e) {
628 throw new RuntimeException("Unhandled", e);
629 }
630 }
631
632
633
634
635
636
637
638
639 @Action(block = BlockingScope.ACTION)
640 public Task<Void, Void> fileExportPng() {
641 final JFileChooser fcPng = createPngChooser(getFile(),
642 "exportPngFileChooser");
643 for (;;) {
644 if (JFileChooser.APPROVE_OPTION != fcPng
645 .showSaveDialog(getMainFrame()))
646 return null;
647 final File dst = gui.ensureSuffix(fcPng.getSelectedFile(), pngPat);
648 if (!askOverwrite(dst))
649 continue;
650
651 return new WaitCursorTask<Void, Void>(this) {
652 @Override
653 protected Void doCursor() throws Exception {
654 renderPng(tactics, dst);
655 return null;
656 }
657 };
658 }
659 }
660
661
662
663
664
665
666
667
668 @Action(enabledProperty = "renderSvgAvailable", block = BlockingScope.ACTION)
669 public Task<Void, Void> fileExportSvg() {
670 final JFileChooser fcSvg = createSvgChooser(getFile(),
671 "exportSvgFileChooser");
672 for (;;) {
673 if (JFileChooser.APPROVE_OPTION != fcSvg
674 .showSaveDialog(getMainFrame()))
675 return null;
676 final File dst = gui.ensureSuffix(fcSvg.getSelectedFile(), svgPat);
677 if (!askOverwrite(dst))
678 continue;
679
680 return new WaitCursorTask<Void, Void>(this) {
681 @Override
682 protected Void doCursor() throws Exception {
683 batik.renderSvg(tactics, dst);
684 return null;
685 }
686 };
687 }
688 }
689
690
691 @Action(block = BlockingScope.APPLICATION)
692 public void fileHammy() {
693 if (!askDiscardUnsaved(gui.findAction("fileHammy")))
694 return;
695 try {
696 setDocument(initialScene);
697 } catch (final IOException e) {
698 throw new RuntimeException("Unhandled", e);
699 }
700 }
701
702
703 @Action
704 public void fileNewDoc() {
705 if (!askDiscardUnsaved(gui.findAction("fileNewDoc")))
706 return;
707 try {
708 setDocument(templateScene);
709 } catch (final IOException e) {
710 throw new RuntimeException("Unhandled", e);
711 }
712 }
713
714
715 @Action
716 public void fileOpen() {
717 if (!askDiscardUnsaved(gui.findAction("fileOpen")))
718 return;
719 final JFileChooser chooser = createJcxChooser(getFile(),
720 "openFileChooser");
721 if (chooser.showOpenDialog(getMainFrame()) == JFileChooser.APPROVE_OPTION) {
722 final File file = chooser.getSelectedFile();
723 try {
724 setDocument(file.toURI().toURL());
725 } catch (final MalformedURLException e) {
726
727 log.warn("File.toURI().toURL() failed", e);
728 } catch (final IOException e) {
729 showErrorDialog("can't open \"" + file + "\"", e);
730 }
731 }
732 };
733
734
735 @Action
736 public void fileOpenURL() {
737 final String a = "fileOpenURL";
738 if (!askDiscardUnsaved(gui.findAction(a)))
739 return;
740 final ResourceMap r = getContext().getResourceMap();
741 final String title = r.getString(a + ".Dialog" + ".title");
742 final String msg = r.getString(a + ".Dialog" + ".message");
743 for (;;) {
744 final String url = JOptionPane.showInputDialog(getMainFrame(), msg,
745 title, JOptionPane.QUESTION_MESSAGE);
746 if (url == null)
747 return;
748 try {
749 setDocument(new URL(url));
750 return;
751 } catch (final IOException e) {
752 showErrorDialog(r.getString(a + ".Dialog" + ".error", url), e);
753 }
754 }
755 }
756
757
758
759
760
761
762 @Action(enabledProperty = "modified")
763 public void fileReset() throws IOException {
764 if (!askDiscardUnsaved(gui.findAction("fileReset")))
765 return;
766 final URL tmp = getDocument();
767 setDocument(null);
768 setDocument(tmp);
769 };
770
771
772 @Action(enabledProperty = "modified")
773 public void fileSave() {
774 if (!isModified())
775 return;
776 final File f = saveHelper(getFile(), getFile(), "saveFileChooser", true);
777 log.info(f);
778 if (f != null) {
779 try {
780 setDocument(f.toURL(), false);
781 } catch (final IOException e) {
782 throw new RuntimeException("Unhandled", e);
783 }
784 setModified(false);
785 }
786 };
787
788
789 @Action
790 public void fileSaveAs() {
791 final File f = saveHelper(null, getFile(), "saveAsFileChooser", false);
792 log.info(f);
793 if (f != null) {
794 try {
795 setDocument(f.toURL(), false);
796 } catch (final IOException e) {
797 throw new RuntimeException("Unhandled", e);
798 }
799 setModified(false);
800 }
801 }
802
803
804 @Action
805 public void fileSaveCopyAs() {
806 log.info(saveHelper(null, getFile(), "saveCopyAsFileChooser", false));
807 }
808
809 private URL getDocument() {
810 return document;
811 }
812
813 private File getFile() {
814 return file;
815 }
816
817 @Action()
818 public void helpDumpProperties() {
819 final List keys = new ArrayList();
820 keys.addAll(System.getProperties().keySet());
821 Collections.sort(keys);
822 for (final Object key : keys)
823 System.out.println(key + "=" + System.getProperty((String) key));
824 }
825
826
827
828
829
830
831 @Override
832 protected void initialize(final String[] as) {
833 if ("Linux".equals(System.getProperty("os.name")))
834 getContext().getResourceManager().setPlatform("linux");
835
836 final Class<?> mc = this.getClass();
837 {
838 final ResourceMap r = Application.getInstance().getContext()
839 .getResourceMap();
840 initialScene = mc.getResource("/" + r.getResourcesDir()
841 + r.getString("Application.defaultDocument"));
842 templateScene = mc.getResource("/" + r.getResourcesDir()
843 + r.getString("Application.templateDocument"));
844 }
845
846
847 document = initialScene;
848 for (final String p : as) {
849
850 if ("-open".equals(p) || "-print".equals(p))
851 continue;
852 try {
853 document = new URL(p);
854 break;
855 } catch (final MalformedURLException e) {
856 final File f = new File(p);
857 if (f.canRead())
858 try {
859 document = f.toURL();
860 break;
861 } catch (final MalformedURLException e2) {
862 log.warn("Cannot load '" + p + "'.", e);
863 }
864 else
865 log.warn("Cannot load '" + p + "'.", e);
866 }
867 }
868 }
869
870 public boolean isAlwaysFalse() {
871 return false;
872 }
873
874 public boolean isCanRedo() {
875 return canRedo;
876 }
877
878 public boolean isCanUndo() {
879 return canUndo;
880 }
881
882 public boolean isModified() {
883 return modified;
884 }
885
886 public boolean isRenderSvgAvailable() {
887 return batik.isBatikAvailable();
888 }
889
890 @Override
891 protected void ready() {
892 final URL tmp = document;
893 document = null;
894 try {
895 setDocument(tmp);
896 } catch (final IOException e) {
897 log.warn("Couldn't load '" + tmp + "'.", e);
898 }
899 addExitListener(new Application.ExitListener() {
900 public boolean canExit(final EventObject e) {
901 return askDiscardUnsaved(gui.findAction("quit"));
902 }
903
904 public void willExit(final EventObject e) {
905 log.info("Good bye!");
906 }
907 });
908 };
909
910 private final void save(final TrajectorySet cts, final File dst)
911 throws IOException {
912 final Cursor cu = switchCursor(waitc);
913 try {
914 final IOTrajectories t = new IOTrajectories();
915
916 t.trajectories().add(cts);
917 new JCurlSerializer().write(t, dst, JDKSerializer.class);
918 } finally {
919 switchCursor(cu);
920 }
921 }
922
923 private File saveHelper(File dst, final File base, final String name,
924 final boolean forceOverwrite) {
925 JFileChooser fcJcx = null;
926 for (;;) {
927 if (fcJcx == null)
928 fcJcx = createJcxChooser(base, name);
929 if (dst == null) {
930 if (JFileChooser.APPROVE_OPTION != fcJcx
931 .showSaveDialog(getMainFrame()))
932 return null;
933 dst = fcJcx.getSelectedFile();
934 }
935 if (dst == null)
936 continue;
937 dst = gui.ensureSuffix(dst, jcxzPat);
938 if (forceOverwrite || askOverwrite(dst))
939 try {
940 save(tactics.getCurves(), dst);
941 return dst;
942 } catch (final Exception e) {
943 showErrorDialog("Couldn't save to '" + dst + "'", e);
944 }
945 else
946 dst = null;
947 }
948 }
949
950 private void setCanRedo(final boolean canRedo) {
951 final boolean old = this.canRedo;
952 if (old == canRedo)
953 return;
954 firePropertyChange("canRedo", old, this.canRedo = canRedo);
955 }
956
957 private void setCanUndo(final boolean canUndo) {
958 final boolean old = this.canUndo;
959 if (old == canUndo)
960 return;
961 firePropertyChange("canUndo", old, this.canUndo = canUndo);
962 }
963
964 private void setDocument(final URL document) throws IOException {
965 this.setDocument(document, true);
966 }
967
968 private void setDocument(final URL document, boolean load)
969 throws IOException {
970 final Cursor cu = switchCursor(waitc);
971 try {
972 log.info(document);
973 final URL old = this.document;
974 this.firePropertyChange("document", old, this.document = document);
975 setFile(this.document);
976 url.setText(this.document == null ? "{null}" : this.document
977 .toString());
978 if (!load)
979 return;
980
981 cm.deregister(tactics.getCurves());
982 final ComputedTrajectorySet cts;
983 if (this.document == null)
984 cts = null;
985 else {
986 final IONode n = new JCurlSerializer().read(this.document);
987 final IOTrajectories it = (IOTrajectories) n;
988 final TrajectorySet ts = it.trajectories().get(0);
989 cts = (ComputedTrajectorySet) ts;
990 }
991 change.discardAllEdits();
992 if (cts != null)
993 cts.setCurrentTime(currentTime);
994 tactics.setCurves(cts);
995 broomSwing.setBroom(tactics.getBroom());
996 cm.register(cts);
997 setModified(false);
998 } finally {
999 switchCursor(cu);
1000 }
1001 }
1002
1003 private void setFile(final URL url) {
1004 File file;
1005 if (url != null && "file".equals(url.getProtocol()))
1006 try {
1007 file = new File(url.toURI());
1008 } catch (final URISyntaxException e) {
1009 file = null;
1010 }
1011 else
1012 file = null;
1013 final File old = this.file;
1014 this.firePropertyChange("file", old, this.file = file);
1015 }
1016
1017 public void setModified(final boolean modified) {
1018 final boolean old = this.modified;
1019 if (old == modified)
1020 return;
1021 firePropertyChange("modified", old, this.modified = modified);
1022 }
1023
1024
1025 @Action(block = BlockingScope.COMPONENT)
1026 public void showAboutBox() {
1027 if (aboutBox == null)
1028 aboutBox = gui.createAboutBox(getMainFrame());
1029 show(aboutBox);
1030 }
1031
1032 private void showErrorDialog(final String message, final Exception e) {
1033 JOptionPane.showMessageDialog(getMainFrame(), "Error: " + message,
1034 "Error", JOptionPane.ERROR_MESSAGE);
1035 }
1036
1037 @Override
1038 protected void startup() {
1039
1040 {
1041 final Image img;
1042 if (true)
1043 img = getContext().getResourceMap().getImageIcon(
1044 "Application.icon").getImage();
1045 else {
1046 final ResourceMap r = getContext().getResourceMap();
1047 if (true)
1048 try {
1049 img = ImageIO.read(this.getClass().getResource(
1050 "/" + r.getResourcesDir() + "/"
1051 + r.getString("Application.icon")));
1052 } catch (final IOException e) {
1053 throw new RuntimeException("Unhandled", e);
1054 }
1055 else
1056 img = Toolkit.getDefaultToolkit().createImage(
1057 this.getClass().getResource(
1058 "/" + r.getResourcesDir() + "/"
1059 + r.getString("Application.icon")));
1060 }
1061 getMainFrame().setIconImage(img);
1062
1063 }
1064
1065
1066 jcxzPat = gui.createFileFilter("fileFilterJcxz", "jcz", "jcx");
1067 pngPat = gui.createFileFilter("fileFilterPng", "png");
1068 svgPat = gui.createFileFilter("fileFilterSvg", "svgz", "svg");
1069
1070 getMainFrame().setJMenuBar(createMenuBar());
1071
1072 final JComponent c = new JPanel();
1073 c.setLayout(new BorderLayout());
1074 tactics.setPreferredSize(new Dimension(400, 600));
1075 c.add(tactics, BorderLayout.CENTER);
1076 c.add(url, BorderLayout.NORTH);
1077 {
1078 final JPanel b = new JPanel();
1079 b.setLayout(new BorderLayout());
1080 final JTabbedPane t = new JTabbedPane(SwingConstants.TOP,
1081 JTabbedPane.SCROLL_TAB_LAYOUT);
1082 t.add("Rock", broomSwing);
1083 t.setMnemonicAt(0, 'R');
1084 t.add("Ice", curlerSwing);
1085 t.setMnemonicAt(1, 'I');
1086 t.add("Collission", new JLabel("TODO: Collission settings"));
1087 t.setMnemonicAt(2, 'C');
1088 b.add(t, BorderLayout.NORTH);
1089 if (false)
1090 b.add(new JLabel("TODO: Bird's eye view"), BorderLayout.CENTER);
1091 else
1092 b.add(birdPiccolo, BorderLayout.CENTER);
1093 c.add(b, BorderLayout.EAST);
1094 }
1095
1096 show(c);
1097 view12Foot();
1098 }
1099
1100 private Cursor switchCursor(final Cursor neo) {
1101 final Cursor cu = getMainFrame().getCursor();
1102 getMainFrame().setCursor(neo);
1103 Thread.yield();
1104 return cu;
1105 }
1106
1107 public void undoableEditHappened(final UndoableEditEvent e) {
1108 setCanRedo(change.canRedo());
1109 setCanUndo(change.canUndo());
1110 }
1111
1112
1113 @Action
1114 public void view12Foot() {
1115 zh.zoom(tactics, ZoomHelper.TwelvePlus, SLOW);
1116 }
1117
1118
1119 @Action
1120 public void viewActive() {
1121 zh.zoom(tactics, ZoomHelper.ActivePlus, SLOW);
1122 }
1123
1124
1125 @Action
1126 public void viewComplete() {
1127 zh.zoom(tactics, ZoomHelper.CompletePlus, SLOW);
1128 }
1129
1130
1131 @Action
1132 public void viewHouse() {
1133 zh.zoom(tactics, ZoomHelper.HousePlus, SLOW);
1134 }
1135
1136
1137 @Action
1138 public void viewPanEast() {
1139 zh.pan(tactics, 0.2, 0, FAST);
1140 }
1141
1142
1143 @Action
1144 public void viewPanNorth() {
1145 zh.pan(tactics, 0, -0.2, FAST);
1146 }
1147
1148
1149 @Action
1150 public void viewPanSouth() {
1151 zh.pan(tactics, 0, 0.2, FAST);
1152 }
1153
1154
1155 @Action
1156 public void viewPanWest() {
1157 zh.pan(tactics, -0.2, 0, FAST);
1158 }
1159
1160
1161 @Action
1162 public void viewZoomIn() {
1163 zh.zoom(tactics, null, 0.75, FAST);
1164 }
1165
1166
1167 @Action
1168 public void viewZoomOut() {
1169 zh.zoom(tactics, null, 1.25, FAST);
1170 }
1171 }