001: // ============================================================================
002: // $Id: Application.java,v 1.8 2005/08/02 23:45:21 davidahall Exp $
003: // Copyright (c) 2004-2005 David A. Hall
004: // ============================================================================
005: // The contents of this file are subject to the Common Development and
006: // Distribution License (CDDL), Version 1.0 (the License); you may not use this
007: // file except in compliance with the License. You should have received a copy
008: // of the the License along with this file: if not, a copy of the License is
009: // available from Sun Microsystems, Inc.
010: //
011: // http://www.sun.com/cddl/cddl.html
012: //
013: // From time to time, the license steward (initially Sun Microsystems, Inc.) may
014: // publish revised and/or new versions of the License. You may not use,
015: // distribute, or otherwise make this file available under subsequent versions
016: // of the License.
017: //
018: // Alternatively, the contents of this file may be used under the terms of the
019: // GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which
020: // case the provisions of the LGPL are applicable instead of those above. If you
021: // wish to allow use of your version of this file only under the terms of the
022: // LGPL, and not to allow others to use your version of this file under the
023: // terms of the CDDL, indicate your decision by deleting the provisions above
024: // and replace them with the notice and other provisions required by the LGPL.
025: // If you do not delete the provisions above, a recipient may use your version
026: // of this file under the terms of either the CDDL or the LGPL.
027: //
028: // This library is distributed in the hope that it will be useful,
029: // but WITHOUT ANY WARRANTY; without even the implied warranty of
030: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
031: // ============================================================================
032:
033: package net.sf.jga.swing.spreadsheet;
034:
035: import java.awt.BorderLayout;
036: import java.awt.Component;
037: import java.awt.Container;
038: import java.awt.Dimension;
039: import java.awt.Insets;
040: import java.awt.Point;
041: import java.awt.event.ActionEvent;
042: import java.awt.event.MouseAdapter;
043: import java.awt.event.MouseEvent;
044: import java.awt.event.WindowAdapter;
045: import java.awt.event.WindowEvent;
046: import java.io.File;
047: import java.io.FileInputStream;
048: import java.io.FileOutputStream;
049: import java.io.IOException;
050: import java.io.InputStream;
051: import java.io.PrintStream;
052: import java.net.MalformedURLException;
053: import java.net.URL;
054: import java.text.MessageFormat;
055: import javax.swing.AbstractAction;
056: import javax.swing.Action;
057: import javax.swing.Icon;
058: import javax.swing.JFileChooser;
059: import javax.swing.JFrame;
060: import javax.swing.JLabel;
061: import javax.swing.JMenu;
062: import javax.swing.JMenuBar;
063: import javax.swing.JMenuItem;
064: import javax.swing.JOptionPane;
065: import javax.swing.JPopupMenu;
066: import javax.swing.JScrollPane;
067: import javax.swing.UIManager;
068: import javax.swing.event.AncestorEvent;
069: import javax.swing.event.AncestorListener;
070: import net.sf.jga.fn.BinaryFunctor;
071: import net.sf.jga.fn.Generator;
072: import net.sf.jga.fn.UnaryFunctor;
073: import net.sf.jga.fn.adaptor.ApplyBinary;
074: import net.sf.jga.fn.adaptor.ConstantBinary;
075: import net.sf.jga.fn.adaptor.ConstantUnary;
076: import net.sf.jga.fn.adaptor.Identity;
077: import net.sf.jga.fn.adaptor.Project1st;
078: import net.sf.jga.fn.adaptor.Project2nd;
079: import net.sf.jga.fn.property.ArrayBinary;
080: import net.sf.jga.fn.property.ArrayUnary;
081: import net.sf.jga.fn.property.InvokeMethod;
082: import net.sf.jga.fn.property.InvokeNoArgMethod;
083: import net.sf.jga.fn.property.SetProperty;
084: import net.sf.jga.parser.FunctorParser;
085: import net.sf.jga.parser.ParseException;
086: import net.sf.jga.swing.GenericAction;
087:
088: /**
089: * An application wrapper for Spreadsheet, providing a main method to
090: * allow for standalone use.
091: * <p>
092: * Copyright © 2004-2005 David A. Hall
093: * @author <a href="mailto:davidahall@users.sf.net">David A. Hall</a>
094: */
095:
096: public class Application {
097:
098: private JFrame _frame;
099:
100: private Spreadsheet _sheet;
101:
102: private FunctorParser _parser;
103:
104: private Controller _controller;
105:
106: private JFileChooser _chooser;
107:
108: public Application() {
109: _sheet = makeDefaultSheet();
110: _parser = new FunctorParser();
111: _parser.bindThis(this );
112:
113: _controller = new Controller(_sheet);
114:
115: _sheet.setUpdateHandler(new SetProperty<Controller, Boolean>(
116: Controller.class, "SheetDirty", Boolean.TYPE).bind(
117: _controller, Boolean.TRUE));
118:
119: // TODO: RunnabableFunctor?
120: createUI(_sheet, _controller);
121: }
122:
123: public Spreadsheet makeDefaultSheet() {
124: Spreadsheet sheet = new Spreadsheet(16, 16);
125: sheet
126: .setPreferredScrollableViewportSize(new Dimension(400,
127: 250));
128: sheet.setEditableByDefault(true);
129:
130: sheet.setRowSelectionInterval(0, 0);
131: sheet.setColumnSelectionInterval(0, 0);
132:
133: return sheet;
134: }
135:
136: public final void createUI(final Spreadsheet sheet,
137: Controller controller) {
138: final JPopupMenu popupMenu = new JPopupMenu("Popup Menu");
139: popupMenu.add(new JMenuItem(controller.getCellRenameCmd()));
140: popupMenu.add(new JMenuItem(controller.getCellFormatCmd()));
141: popupMenu.add(new JMenuItem(controller.getCellTypeCmd()));
142:
143: // Add a rightclick mouse listener with a 'standard' popup menu
144: sheet.addMouseListener(new MouseAdapter() {
145: public void mousePressed(MouseEvent e) {
146: // Right clicking should update selection
147: if (e.getButton() == e.BUTTON3) {
148: Spreadsheet sht = (Spreadsheet) e.getComponent();
149: Point p = e.getPoint();
150: int row = sht.rowAtPoint(p);
151: int col = sht.columnAtPoint(p);
152: sht.setRowSelectionInterval(row, row);
153: sht.setColumnSelectionInterval(col, col);
154:
155: if (e.isPopupTrigger()) {
156: popupMenu.show(e.getComponent(), p.x, p.y);
157: }
158: }
159: }
160:
161: public void mouseReleased(MouseEvent e) {
162: if (e.isPopupTrigger()) {
163: popupMenu
164: .show(e.getComponent(), e.getX(), e.getY());
165: }
166: }
167: });
168:
169: // Setup a status bar, and wire it to the spreadsheet's status functor
170: JLabel statusLabel = new JLabel("cell(0,0)");
171: sheet.setStatusHandler(new SetProperty(JLabel.class, "Text",
172: String.class).bind1st(statusLabel));
173:
174: // Setup the outer frame
175: _frame = new JFrame("Application");
176: _frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
177: _frame.addWindowListener(new WindowAdapter() {
178: public void windowClosing(WindowEvent e) {
179: closeWorksheet();
180: // System.out.println("Calling System.exit()");
181: System.exit(0);
182: }
183: });
184:
185: // wire up the various functors that implement user interaction
186: controller.setLoadFunctor(buildLoadFunctor());
187: controller.setSaveFunctor(buildSaveFunctor());
188: controller.setErrorFunctor(buildErrorFunctor());
189: controller.setPromptFunctor(buildPromptFunctor());
190: controller.setConfirmFunctor(buildConfirmFunctor());
191:
192: // Setup the frame's content pane
193: Container rootPane = _frame.getContentPane();
194: rootPane.setLayout(new BorderLayout(5, 5));
195:
196: JScrollPane pane = new JScrollPane(sheet);
197: rootPane.add(pane, BorderLayout.CENTER);
198: rootPane.add(statusLabel, BorderLayout.SOUTH);
199:
200: // Setup the frame's menu
201: JMenu fileMenu = new JMenu("File");
202: fileMenu.add(controller.getFileNewCmd());
203: fileMenu.add(controller.getFileOpenCmd());
204: fileMenu.add(controller.getFileSaveCmd());
205: fileMenu.add(controller.getFileSaveAsCmd());
206: fileMenu.add(getFileExitCmd());
207:
208: JMenu defaultMenu = new JMenu("Default");
209: defaultMenu.add(controller.getDefaultEditableCmd());
210: defaultMenu.add(controller.getDefaultTypeCmd());
211: defaultMenu.add(controller.getDefaultValueCmd());
212: // defaultMenu.add(controller.getDefaultFormatCmd());
213: // defaultMenu.add(controller.getDefaultRendererCmd());
214: // defaultMenu.add(controller.getDefaultEditorCmd());
215:
216: JMenu sheetMenu = new JMenu("Worksheet");
217: sheetMenu.add(controller.getSheetColumnsCmd());
218: sheetMenu.add(controller.getSheetRowsCmd());
219: sheetMenu.add(defaultMenu);
220: sheetMenu.add(controller.getImportClassCmd());
221:
222: JMenu cellMenu = new JMenu("Cell");
223: cellMenu.add(controller.getCellRenameCmd());
224: // cellMenu.add(controller.getCellTypeCmd());
225: // cellMenu.add(controller.getCellFormatCmd());
226: // cellMenu.add(controller.getCellValidatorCmd());
227: // cellMenu.add(controller.getCellRendererCmd());
228: // cellMenu.add(controller.getCellEditorCmd());
229:
230: JMenuBar menu = new JMenuBar();
231: menu.add(fileMenu);
232: menu.add(sheetMenu);
233: menu.add(cellMenu);
234:
235: // show the frame
236: _frame.setJMenuBar(menu);
237: _frame.pack();
238: _frame.show();
239: }
240:
241: // ----------------------
242: // Spreadsheet UI Methods
243: // ----------------------
244:
245: /**
246: * Returns an Action that closes the spreadsheet.
247: */
248: public Action getFileExitCmd() {
249: UnaryFunctor<ActionEvent, ?> fn = new Project2nd<ActionEvent, Object>()
250: .generate2nd(new InvokeNoArgMethod(Application.class,
251: "closeWorksheet").bind(this ));
252:
253: return new GenericAction(fn, "Exit");
254: }
255:
256: // ----------------------
257: // Spreadsheet IO Methods
258: // ----------------------
259:
260: public int loadFile(Spreadsheet sheet) {
261: if (getChooser().showOpenDialog(_frame) != JFileChooser.APPROVE_OPTION)
262: return Controller.CANCEL_OPTION;
263:
264: File file = getChosenFile();
265: if (file == null)
266: return Controller.CANCEL_OPTION;
267:
268: try {
269: FileInputStream fis = new FileInputStream(file);
270: sheet.readSpreadsheet(fis);
271: _controller.setSheetSource(file.toURL());
272: return Controller.YES_OPTION;
273: } catch (IOException x) {
274: _controller.notify(x.getMessage(), _controller
275: .getExceptionName(x));
276: return Controller.CANCEL_OPTION;
277: }
278: }
279:
280: public int saveFile(Spreadsheet sheet, boolean promptForName) {
281: URL hint = _controller.getSheetSource();
282: if (promptForName || hint == null)
283: if (getChooser().showSaveDialog(_frame) != JFileChooser.APPROVE_OPTION)
284: return Controller.CANCEL_OPTION;
285:
286: File file = getChosenFile();
287: if (file != null) {
288: try {
289: FileOutputStream fos = new FileOutputStream(file);
290: _controller.setSheetSource(file.toURL());
291: _sheet.writeSpreadsheet(fos);
292: fos.close();
293: _controller.setSheetDirty(false);
294: return Controller.YES_OPTION;
295: } catch (IOException x) {
296: Throwable t = _controller.getRootCause(x);
297: _controller.notify(x.getMessage(), _controller
298: .getExceptionName(t));
299: }
300: }
301:
302: return Controller.CANCEL_OPTION;
303: }
304:
305: // ============================================================
306: // ============================================================
307:
308: public void closeWorksheet() {
309: int ans = Controller.YES_OPTION;
310: if (_controller.isSheetDirty()) {
311: ans = _controller.promptAndSave();
312: }
313:
314: if (ans != Controller.CANCEL_OPTION) {
315: _frame.dispose();
316: }
317: }
318:
319: private JFileChooser getChooser() {
320: if (_chooser != null)
321: return _chooser;
322:
323: File pwd = new File(".");
324: _chooser = new JFileChooser(pwd);
325:
326: // // Note: source for ExampleFileFilter can be found in FileChooserDemo,
327: // // under the demo/jfc directory in the Java 2 SDK, Standard Edition.
328: // ExampleFileFilter filter = new ExampleFileFilter();
329: // filter.addExtension("jpg");
330: // filter.setDescription("JPG & GIF Images");
331: // chooser.setFileFilter(filter);
332:
333: return _chooser;
334: }
335:
336: private File getChosenFile() {
337: File dir = _chooser.getCurrentDirectory();
338: if (dir == null)
339: return null;
340:
341: return _chooser.getSelectedFile();
342: }
343:
344: // =========================
345: // protected API
346: // =========================
347:
348: protected Controller getController() {
349: return _controller;
350: }
351:
352: // =========================
353: // Implementation Details
354: // =========================
355:
356: public BinaryFunctor<String, String, String> buildPromptFunctor() {
357: // This gets a little hairy. We have have to manually assemble the arguments
358: // to the JOptionPane.showInputDialog class since we want to bind to the component
359: // that is available here and leave the two arguments to the caller.
360: InvokeMethod<JOptionPane, String> showInput = new InvokeMethod<JOptionPane, String>(
361: JOptionPane.class, "showInputDialog", new Class[] {
362: Component.class, Object.class, Object.class });
363:
364: ApplyBinary<String, String> threeArgs = new ApplyBinary<String, String>(
365: new BinaryFunctor[] {
366: new ConstantBinary<String, String, Component>(
367: _frame),
368: new Project1st<String, String>(),
369: new Project2nd<String, String>() });
370:
371: return showInput.bind1st(null).compose(threeArgs);
372: }
373:
374: public BinaryFunctor<String, String, ?> buildErrorFunctor() {
375: // This is fairly similar to the PromptFunctor. This one requires
376: // four parms, two of which are constants.
377: InvokeMethod<JOptionPane, ?> showError = new InvokeMethod<JOptionPane, Object>(
378: JOptionPane.class, "showMessageDialog", new Class[] {
379: Component.class, Object.class, String.class,
380: Integer.TYPE });
381:
382: ApplyBinary<String, String> fourArgs = new ApplyBinary<String, String>(
383: new BinaryFunctor[] {
384: new ConstantBinary<String, String, Component>(
385: _frame),
386: new Project1st<String, String>(),
387: new Project2nd<String, String>(),
388: new ConstantBinary<String, String, Integer>(
389: JOptionPane.ERROR_MESSAGE) });
390:
391: return showError.bind1st(null).compose(fourArgs);
392: }
393:
394: public BinaryFunctor<String, String, Integer> buildConfirmFunctor() {
395: InvokeMethod showConfirm = new InvokeMethod(JOptionPane.class,
396: "showConfirmDialog", new Class[] { Component.class,
397: Object.class, String.class, Integer.TYPE });
398:
399: ApplyBinary<String, String> fourArgs = new ApplyBinary<String, String>(
400: new BinaryFunctor[] {
401: new ConstantBinary<String, String, Component>(
402: _frame),
403: new Project1st<String, Boolean>(),
404: new Project2nd<String, Boolean>(),
405: new ConstantBinary<String, String, Integer>(
406: JOptionPane.YES_NO_CANCEL_OPTION) });
407:
408: return showConfirm.bind1st(null).compose(fourArgs);
409: }
410:
411: public BinaryFunctor<Spreadsheet, Boolean, Integer> buildSaveFunctor() {
412: InvokeMethod<Application, Integer> getSave = new InvokeMethod<Application, Integer>(
413: Application.class, "saveFile", new Class[] {
414: Spreadsheet.class, Boolean.TYPE });
415: return getSave.bind1st(this ).compose(
416: new ArrayBinary<Spreadsheet, Boolean>());
417: }
418:
419: public UnaryFunctor<Spreadsheet, Integer> buildLoadFunctor() {
420: InvokeMethod<Application, Integer> getLoad = new InvokeMethod<Application, Integer>(
421: Application.class, "loadFile",
422: new Class[] { Spreadsheet.class });
423: return getLoad.bind1st(this ).compose(
424: new ArrayUnary<Spreadsheet>());
425: }
426:
427: // ------------------------
428: // Standalone entry point
429: // ------------------------
430:
431: static public void main(String[] args) {
432: printStartupHeader();
433:
434: try {
435: UIManager.setLookAndFeel(UIManager
436: .getCrossPlatformLookAndFeelClassName());
437: } catch (Exception x) {
438: // TODO: log this instead of simply dumping it
439: System.err.println("Error loading L&F:" + x);
440: }
441:
442: // TODO: do command line processing
443:
444: new Application();
445: }
446:
447: static private void printStartupHeader() {
448: System.out.println("");
449: System.out.println("/**");
450: System.out.println(" * A Java Hacker's Worksheet");
451: System.out.println(" * Copyright (c) 2004-2005 David A. Hall");
452: System.out.println(" */");
453: System.out.println("");
454: }
455: }
|