001: // ============================================================================
002: // $Id: Controller.java,v 1.7 2005/12/17 04:45:03 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.Component;
036: import java.awt.Point;
037: import java.awt.event.ActionEvent;
038: import java.awt.event.MouseAdapter;
039: import java.awt.event.MouseEvent;
040: import java.io.File;
041: import java.io.FileOutputStream;
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.io.OutputStream;
045: import java.net.MalformedURLException;
046: import java.net.URL;
047: import java.text.MessageFormat;
048: import javax.swing.Action;
049: import javax.swing.JFileChooser;
050: import javax.swing.JMenuItem;
051: import javax.swing.JOptionPane;
052: import javax.swing.JPopupMenu;
053: import net.sf.jga.fn.BinaryFunctor;
054: import net.sf.jga.fn.Generator;
055: import net.sf.jga.fn.UnaryFunctor;
056: import net.sf.jga.fn.adaptor.Constant;
057: import net.sf.jga.fn.adaptor.ConstantBinary;
058: import net.sf.jga.fn.adaptor.ConstantUnary;
059: import net.sf.jga.fn.adaptor.Identity;
060: import net.sf.jga.fn.adaptor.Project2nd;
061: import net.sf.jga.fn.property.InvokeNoArgMethod;
062: import net.sf.jga.parser.FunctorParser;
063: import net.sf.jga.parser.ParseException;
064: import net.sf.jga.swing.GenericAction;
065:
066: /**
067: * Provides for generally common operations on a spreadsheet when it is in
068: * an application.
069: * <p>
070: * Copyright © 2004-2005 David A. Hall
071: * @author <a href="mailto:davidahall@users.sf.net">David A. Hall</a>
072: */
073:
074: public class Controller {
075:
076: // The spreadsheet being controlled
077: private Spreadsheet _sheet;
078:
079: // A parser for constructing various functors for the sheet
080: private FunctorParser _parser;
081:
082: // The functor executed when the user must be prompted for simple information
083: private BinaryFunctor<String, String, String> _promptFn = new ConstantBinary<String, String, String>(
084: "");
085:
086: // The functor executed when the user is asked a yes/no/cancel question
087: private BinaryFunctor<String, String, Integer> _confirmFn = new ConstantBinary<String, String, Integer>(
088: null);
089:
090: // The functor executed to report errors to the user
091: private BinaryFunctor<String, String, ?> _errorFn = new ConstantBinary<String, String, String>(
092: "");
093:
094: // The functor executed to load a spreadsheet
095: private UnaryFunctor<Spreadsheet, Integer> _loadFn = new ConstantUnary<Spreadsheet, Integer>(
096: null);
097:
098: // The functor executed to save a spreadsheet
099: private BinaryFunctor<Spreadsheet, Boolean, Integer> _saveFn = new ConstantBinary<Spreadsheet, Boolean, Integer>(
100: null);
101:
102: public static final int YES_OPTION = JOptionPane.YES_OPTION;
103: public static final int NO_OPTION = JOptionPane.NO_OPTION;
104: public static final int CANCEL_OPTION = JOptionPane.CANCEL_OPTION;
105:
106: /**
107: * Builds a Controller to control the given sheet widget.
108: */
109: public Controller(Spreadsheet sheet) {
110: _sheet = sheet;
111: _parser = new FunctorParser();
112: _parser.bindThis(this );
113: }
114:
115: /**
116: * Prompts the user for a string value.
117: * @param msg the description of the string to be shown to the user
118: * @param val the default or existing value of the string
119: */
120: public String prompt(String msg, String val) {
121: return _promptFn.fn(msg, val);
122: }
123:
124: /**
125: * Sets the functor used to prompt the user for simple information.
126: */
127: public void setPromptFunctor(
128: BinaryFunctor<String, String, String> fn) {
129: _promptFn = fn;
130: }
131:
132: /**
133: * Asks the user a Yes/No/Cancel question.
134: * @return a value taken fro
135: */
136:
137: public int confirm(String msg, String title) {
138: return _confirmFn.fn(msg, title);
139: }
140:
141: /**
142: * Sets the functor used to ask the user a yes/no/cancel question
143: */
144:
145: public void setConfirmFunctor(
146: BinaryFunctor<String, String, Integer> fn) {
147: _confirmFn = fn;
148: }
149:
150: /**
151: * Displays an error message to the user
152: */
153:
154: public void notify(String msg, String title) {
155: _errorFn.fn(msg, title);
156: }
157:
158: /**
159: * Sets the functor used to display an error message.
160: */
161: public void setErrorFunctor(BinaryFunctor<String, String, ?> fn) {
162: _errorFn = fn;
163: }
164:
165: /**
166: * Loads a spreadsheet
167: */
168:
169: public int loadSheet(Spreadsheet sheet) {
170: return _loadFn.fn(sheet);
171: }
172:
173: /**
174: * Sets the functor used to get an output stream
175: */
176:
177: public void setLoadFunctor(UnaryFunctor<Spreadsheet, Integer> fn) {
178: _loadFn = fn;
179: }
180:
181: /**
182: * Saves a spreadsheet
183: */
184:
185: public int saveSheet(Spreadsheet sheet, boolean prompt) {
186: return _saveFn.fn(sheet, prompt);
187: }
188:
189: /**
190: * Sets the functor used to get an output stream
191: */
192:
193: public void setSaveFunctor(
194: BinaryFunctor<Spreadsheet, Boolean, Integer> fn) {
195: _saveFn = fn;
196: }
197:
198: // ----------------------
199: // Spreadsheet UI Methods
200: // ----------------------
201:
202: /**
203: * Returns an Action that resets the spreadsheet to default state.
204: */
205: public Action getFileNewCmd() {
206: return new GenericAction(parseAction("this.newWorksheet()"),
207: "New");
208: }
209:
210: /**
211: * Returns an Action that sets the spreadsheet to the contents of a file.
212: */
213: public Action getFileOpenCmd() {
214: return new GenericAction(parseAction("this.openFile()"), "Open");
215: }
216:
217: // TODO: saveFile should only be enabled when the file is dirty
218:
219: /**
220: * Returns an Action that saves the spreadsheet to the file from whence it came.
221: */
222: public Action getFileSaveCmd() {
223: return new GenericAction(parseAction("this.saveFile()"), "Save");
224: }
225:
226: // TODO: saveFileAs should only be enabled when the file is dirty
227:
228: /**
229: * Returns an Action that saves the spreadsheet to a file.
230: */
231: public Action getFileSaveAsCmd() {
232: return new GenericAction(parseAction("this.saveFileAs()"),
233: "Save As...");
234: }
235:
236: /**
237: * Returns an Action that changes the default cell format.
238: */
239: public Action getDefaultEditableCmd() {
240: GenericAction cmd = new GenericAction(
241: new Identity<ActionEvent>(), "Editable");
242: cmd.setEnabled(false);
243: return cmd;
244: }
245:
246: /**
247: * Returns an Action that changes the default cell type.
248: */
249: public Action getDefaultTypeCmd() {
250: return new GenericAction(parseAction("this.setDefaultType()"),
251: "Cell Type");
252: }
253:
254: /**
255: * Returns an Action that changes the default cell type.
256: */
257: public Action getDefaultValueCmd() {
258: return new GenericAction(parseAction("this.setDefaultValue()"),
259: "Cell Value");
260: }
261:
262: /**
263: * Returns an Action that changes the default cell format.
264: */
265: public Action getDefaultFormatCmd() {
266: GenericAction cmd = new GenericAction(
267: new Identity<ActionEvent>(), "Format");
268: cmd.setEnabled(false);
269: return cmd;
270: }
271:
272: /**
273: * Returns an Action that changes the default cell renderer.
274: */
275: public Action getDefaultRendererCmd() {
276: GenericAction cmd = new GenericAction(
277: new Identity<ActionEvent>(), "Renderer");
278: cmd.setEnabled(false);
279: return cmd;
280: }
281:
282: /**
283: * Returns an Action that changes the default cell editor.
284: */
285: public Action getDefaultEditorCmd() {
286: GenericAction cmd = new GenericAction(
287: new Identity<ActionEvent>(), "Editor");
288: cmd.setEnabled(false);
289: return cmd;
290: }
291:
292: /**
293: * Returns an Action that resizes the worksheet.
294: */
295: public Action getSheetColumnsCmd() {
296: return new GenericAction(parseAction("this.setColumnCount()"),
297: "Set Column Count");
298: }
299:
300: /**
301: * Returns an Action that resizes the worksheet.
302: */
303: public Action getSheetRowsCmd() {
304: return new GenericAction(parseAction("this.setRowCount()"),
305: "Set Row Count");
306: }
307:
308: /**
309: * Returns an Action that allows the user to import a class.
310: */
311: public Action getImportClassCmd() {
312: GenericAction cmd = new GenericAction(
313: new Identity<ActionEvent>(), "Import Class");
314: cmd.setEnabled(false);
315: return cmd;
316: }
317:
318: /**
319: * Returns an Action that renames the current cell.
320: */
321: public Action getCellRenameCmd() {
322: String cellNameExp = "this.setCellName(x.getSelectedRow(),x.getSelectedColumn())";
323: try {
324: UnaryFunctor<ActionEvent, Object> cellNameFn = new Project2nd<ActionEvent, Object>()
325: .generate2nd(_parser.parseUnary(cellNameExp,
326: Spreadsheet.class).bind(_sheet));
327:
328: return new GenericAction(cellNameFn, "Set Name");
329: } catch (ParseException x) {
330: x.printStackTrace();
331: GenericAction cmd = new GenericAction(
332: new ConstantUnary<ActionEvent, Object>(null),
333: "Set Name");
334: cmd.setEnabled(false);
335: return cmd;
336: }
337: }
338:
339: /**
340: * Returns an Action that reformats the current cell.
341: */
342: public Action getCellFormatCmd() {
343: GenericAction cmd = new GenericAction(
344: new Identity<ActionEvent>(), "Set Format");
345: cmd.setEnabled(false);
346: return cmd;
347: }
348:
349: /**
350: * Returns an Action that changes the type of the current cell.
351: */
352: public Action getCellTypeCmd() {
353: GenericAction cmd = new GenericAction(
354: new Identity<ActionEvent>(), "Set Type");
355: cmd.setEnabled(false);
356: return cmd;
357: }
358:
359: /**
360: * Returns an Action that changes the type of the current cell.
361: */
362: public Action getCellRendererCmd() {
363: GenericAction cmd = new GenericAction(
364: new Identity<ActionEvent>(), "Set Renderer");
365: cmd.setEnabled(false);
366: return cmd;
367: }
368:
369: /**
370: * Returns an Action that changes the type of the current cell.
371: */
372: public Action getCellEditorCmd() {
373: GenericAction cmd = new GenericAction(
374: new Identity<ActionEvent>(), "Set Editor");
375: cmd.setEnabled(false);
376: return cmd;
377: }
378:
379: /**
380: * Returns an Action that changes the type of the current cell.
381: */
382: public Action getCellValidatorCmd() {
383: GenericAction cmd = new GenericAction(
384: new Identity<ActionEvent>(), "Set Validator");
385: cmd.setEnabled(false);
386: return cmd;
387: }
388:
389: // ----------------------
390: // Worksheet Methods
391: // ----------------------
392:
393: /**
394: * Prompts the user for the cell type.
395: */
396: public void setDefaultType() {
397: String prompt = "Enter default type for uninitialized cells";
398: String name = _promptFn.fn(prompt, _sheet.getDefaultCellType()
399: .getName());
400: if (name == null)
401: return;
402:
403: try {
404: _sheet.setDefaultCellType(Class.forName(name));
405: } catch (ClassNotFoundException x) {
406: String fmt = "Class {0} not found";
407: String msg = MessageFormat.format(fmt, new Object[] { x
408: .getMessage() });
409: _errorFn.fn(msg, "ClassNotFoundException");
410: }
411: }
412:
413: /**
414: * Prompts the user for the number of columns
415: */
416: public void setColumnCount() {
417: String prompt = "Enter the number of columns";
418: String countstr = _promptFn.fn(prompt, String.valueOf(_sheet
419: .getColumnCount()));
420: if (countstr == null)
421: return;
422:
423: try {
424: _sheet.setColumnCount(Integer.parseInt(countstr));
425: } catch (NumberFormatException x) {
426: String fmt = "{0} isn't a number";
427: String msg = MessageFormat.format(fmt,
428: new Object[] { countstr });
429: _errorFn.fn(msg, "NumberFormatException");
430: }
431: }
432:
433: /**
434: * Prompts the user for the number of rows
435: */
436: public void setRowCount() {
437: String prompt = "Enter the number of rows";
438: String countstr = _promptFn.fn(prompt, String.valueOf(_sheet
439: .getRowCount()));
440: if (countstr == null)
441: return;
442:
443: try {
444: _sheet.setRowCount(Integer.parseInt(countstr));
445: } catch (NumberFormatException x) {
446: String fmt = "{0} isn't a number";
447: String msg = MessageFormat.format(fmt,
448: new Object[] { countstr });
449: _errorFn.fn(msg, "NumberFormatException");
450: }
451: }
452:
453: // TODO: need to save the formula and use it in the prompt, instead of the current
454: // default value represented as a string. If the user, for example, sets the default
455: // value to "new java.util.Date()", on the next prompt, he'll get the point in time
456: // in which the expression was evaluated, which itself is not parsable.
457:
458: /**
459: * Prompts the user for the default cell value.
460: */
461: public void setDefaultValue() {
462: String prompt = "Enter default value for uninitialized cells";
463: String exp = _promptFn.fn(prompt, ""
464: + _sheet.getDefaultCellValue());
465: if (exp == null)
466: return;
467:
468: try {
469: Generator gen = _sheet.getParser().parseGenerator(exp);
470: _sheet.setDefaultCellValue(gen.gen());
471: } catch (ParseException x) {
472: _errorFn.fn(x.getMessage(),
473: getExceptionName(getRootCause(x)));
474: }
475: }
476:
477: // ----------------------
478: // Cell Methods
479: // ----------------------
480: /**
481: * Prompts the user for the name of the designated cell.
482: */
483: public void setCellName(int row, int col) {
484: String prompt = "Enter name for cell(" + row + "," + col + ")";
485: Cell cell = _sheet.getCellIfPresent(row, col);
486: String oldName = (cell != null) ? cell.getName() : "";
487: String name = _promptFn.fn(prompt, oldName);
488: if (name != null && !name.equals(oldName))
489: _sheet.setCellName(name, row, col);
490: }
491:
492: // ----------------------
493: // Spreadsheet IO Methods
494: // ----------------------
495:
496: /**
497: * Creates a new, empty spreadsheet with default settings.
498: */
499: public void newWorksheet() {
500: int ans = JOptionPane.YES_OPTION;
501: if (isSheetDirty()) {
502: ans = promptAndSave();
503: }
504:
505: if (ans != JOptionPane.CANCEL_OPTION) {
506: _sheet.clear();
507: setSheetSource(null);
508: setSheetDirty(false);
509: }
510: }
511:
512: /**
513: * Prompts the user for a file to open, and sets the spreadsheet to the file's contents.
514: */
515: public int openFile() {
516: return loadSheet(_sheet);
517: }
518:
519: /**
520: */
521: public int saveFile() {
522: return saveSheet(_sheet, false);
523: }
524:
525: /**
526: */
527: public int saveFileAs() {
528: return saveSheet(_sheet, true);
529: }
530:
531: /**
532: * Prompts the user using a supplied functor, and returns one of YES, NO, or CANCEL
533: */
534: public int promptAndSave() {
535: String format = "save {0}?";
536: Object name = getSheetSource();
537: if (name == null)
538: name = "worksheet";
539:
540: String msg = MessageFormat
541: .format(format, new Object[] { name });
542: int ans = confirm(msg, msg);
543: if (ans == Controller.YES_OPTION) {
544: int choice = saveFile();
545: if (choice == Controller.CANCEL_OPTION
546: || choice == JFileChooser.ERROR_OPTION) {
547: ans = Controller.CANCEL_OPTION;
548: }
549: }
550: return ans;
551: }
552:
553: // ------------------------
554: // Implementation Details
555: // ------------------------
556:
557: private final String DIRTY_PROP = "dirty";
558:
559: public boolean isSheetDirty() {
560: Object dirtyProp = _sheet.getClientProperty(DIRTY_PROP);
561: return Boolean.TRUE.equals(dirtyProp);
562: }
563:
564: public void setSheetDirty(boolean flag) {
565: _sheet.putClientProperty(DIRTY_PROP, Boolean.valueOf(flag));
566: }
567:
568: private final String SOURCE_PROP = "source";
569:
570: public URL getSheetSource() {
571: Object sourceURL = _sheet.getClientProperty(SOURCE_PROP);
572: if (sourceURL == null)
573: return null;
574:
575: if (sourceURL instanceof URL)
576: return (URL) sourceURL;
577:
578: try {
579: return new File(new File("."), "worksheet1.hwks").toURL();
580: } catch (MalformedURLException x) {
581: x.printStackTrace();
582: return null;
583: }
584: }
585:
586: public void setSheetSource(URL url) {
587: // System.out.println("setSheetSource(" + url + ")");
588: _sheet.putClientProperty(SOURCE_PROP, url);
589: }
590:
591: private UnaryFunctor<ActionEvent, ?> parseAction(String exp) {
592: try {
593: return new Project2nd<ActionEvent, Object>()
594: .generate2nd(_parser.parseGenerator(exp));
595: } catch (ParseException x) {
596: x.printStackTrace();
597: // TODO: return some default that won't kill us
598: return null;
599: }
600: }
601:
602: // ===============================================================
603:
604: static Throwable getRootCause(Throwable t) {
605: for (Throwable t1 = t.getCause(); t1 != null; t1 = t.getCause())
606: t = t1;
607:
608: return t;
609: }
610:
611: static String getExceptionName(Throwable t) {
612: String fqcn = t.getClass().getName();
613: return fqcn.substring(fqcn.lastIndexOf(".") + 1);
614: }
615: }
|