001    /*
002     * Copyright (c) 2005 Einar Pehrson <einar@pehrson.nu>.
003     *
004     * This file is part of
005     * CleanSheets - a spreadsheet application for the Java platform.
006     *
007     * CleanSheets is free software; you can redistribute it and/or modify
008     * it under the terms of the GNU General Public License as published by
009     * the Free Software Foundation; either version 2 of the License, or
010     * (at your option) any later version.
011     *
012     * CleanSheets is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015     * GNU General Public License for more details.
016     *
017     * You should have received a copy of the GNU General Public License
018     * along with CleanSheets; if not, write to the Free Software
019     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
020     */
021    package csheets;
022    
023    import java.io.File;
024    import java.io.FileInputStream;
025    import java.io.FileNotFoundException;
026    import java.io.FileOutputStream;
027    import java.io.IOException;
028    import java.io.InputStream;
029    import java.text.DateFormat;
030    import java.util.ArrayList;
031    import java.util.Collection;
032    import java.util.Date;
033    import java.util.HashMap;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.Properties;
037    
038    import javax.swing.SwingUtilities;
039    
040    import csheets.core.Workbook;
041    import csheets.core.formula.compiler.FormulaCompiler;
042    import csheets.core.formula.lang.Language;
043    import csheets.ext.ExtensionManager;
044    import csheets.io.Codec;
045    import csheets.io.CodecFactory;
046    import csheets.io.NamedProperties;
047    
048    /**
049     * CleanSheets - the main class of the application.
050     * The class manages workbooks, performs I/O operations and provides support
051     * for notifying listeners when workbooks are created, loaded or saved.
052     * @author Einar Pehrson
053     */
054    public class CleanSheets {
055    
056            /** The filename of the default properties, loaded from the directory of the class */
057            private static final String DEFAULT_PROPERTIES_FILENAME = "res/defaults.xml";
058    
059            /** The filename of the user properties, loaded from the user's current working directory */
060            private static final String USER_PROPERTIES_FILENAME = "csheets.xml";
061    
062            /** The open workbooks */
063            private Map<Workbook, File> workbooks = new HashMap<Workbook, File>();
064    
065            /** The application's properties */
066            private NamedProperties props;
067    
068            /** The listeners registered to receive events */
069            private List<SpreadsheetAppListener> listeners
070                    = new ArrayList<SpreadsheetAppListener>();
071    
072            /**
073             * Creates the CleanSheets application.
074             */
075            public CleanSheets() {
076                    // Loads compilers
077                    FormulaCompiler.getInstance();
078    
079                    // Loads language
080                    Language.getInstance();
081    
082                    // Loads extensions
083                    ExtensionManager.getInstance();
084    
085                    // Loads default properties
086                    Properties defaultProps = new Properties();
087                    InputStream defaultStream = CleanSheets.class.getResourceAsStream(DEFAULT_PROPERTIES_FILENAME);
088                    if (defaultStream != null)
089                            try {
090                                    defaultProps.loadFromXML(defaultStream);
091                            } catch (IOException e) {
092                                    System.err.println("Could not load default application properties.");
093                            } finally {
094                                    try {
095                                            if (defaultStream != null)
096                                                    defaultStream.close();
097                                    } catch (IOException e) {}
098                            }
099    
100                    // Loads user properties
101                    File propsFile = new File(USER_PROPERTIES_FILENAME);
102                    props = new NamedProperties(propsFile, defaultProps);
103            }
104    
105            /**
106             * Starts CleanSheets from the command-line.
107             * @param args the command-line arguments (not used)
108             */
109            public static void main(String[] args) {
110                    CleanSheets app = new CleanSheets();
111    
112                    // Configures look and feel
113                    javax.swing.JFrame.setDefaultLookAndFeelDecorated(true);
114                    javax.swing.JDialog.setDefaultLookAndFeelDecorated(true);
115                    /* try {
116                            javax.swing.UIManager.setLookAndFeel("className");
117                    } catch (Exception e) {} */
118    
119                    // Creates user interface
120                    new csheets.ui.Frame.Creator(app).createAndWait();
121                    app.create();
122            }
123    
124            /**
125             * Returns the current user properties.
126             * @return the current user properties
127             */
128            public Properties getUserProperties() {
129                    return props;
130            }
131    
132            /**
133             * Exits the application.
134             */
135            public void exit() {
136                    // Stores properties
137                    if (props.size() > 0)
138                            try {
139                                    props.storeToXML("CleanSheets User Properties (" + 
140                                            DateFormat.getDateTimeInstance().format(new Date()) + ")");
141                            } catch (IOException e) {
142                                    System.err.println("An error occurred while saving properties.");
143                            }
144    
145                    // Terminates the virtual machine
146                    System.exit(0);
147            }
148    
149            /**
150             * Creates a new workbook.
151             */
152            public void create() {
153                    Workbook workbook = new Workbook(3);
154                    workbooks.put(workbook, null);
155                    fireSpreadsheetAppEvent(workbook, null, SpreadsheetAppEvent.Type.CREATED);
156            }
157    
158            /**
159             * Loads a workbook from the given file.
160             * @param file the file in which the workbook is stored
161             * @throws IOException if the file could not be loaded correctly
162             */
163            public void load(File file) throws IOException, ClassNotFoundException {
164                    Codec codec = new CodecFactory().getCodec(file);
165                    if (codec != null) {
166                            FileInputStream stream = null;
167                            Workbook workbook;
168                            try {
169                                    // Reads workbook data
170                                    stream = new FileInputStream(file);
171                                    workbook = codec.read(stream);
172                            } finally {
173                                    try {
174                                            if (stream != null)
175                                                    stream.close();
176                                    } catch (IOException e) {}
177                            }
178    
179                            // Loads the workbook
180                            workbooks.put(workbook, file);
181                            fireSpreadsheetAppEvent(workbook, file, SpreadsheetAppEvent.Type.LOADED);
182                    } else
183                            throw new IOException("Codec could not be found");
184            }
185    
186            /**
187             * Unloads the given workbook.
188             * @param workbook the workbook to unload
189             */
190            public void unload(Workbook workbook) {
191                    File file = workbooks.remove(workbook);
192                    fireSpreadsheetAppEvent(workbook, file, SpreadsheetAppEvent.Type.UNLOADED);
193            }
194    
195            /**
196             * Saves the given workbook to the file from which it was loaded,
197             * or to which it was most recently saved.
198             * @param workbook the workbook to save
199             * @throws IOException if the file could not be saved correctly
200             */
201            public void save(Workbook workbook) throws IOException {
202                    File file = workbooks.get(workbook);
203                    if (file != null)
204                            saveAs(workbook, file);
205                    else
206                            throw new FileNotFoundException("No file assigned to the workbook.");
207            }
208    
209            /**
210             * Saves the given workbook to the given file.
211             * @param workbook the workbook to save
212             * @param file the file to which the workbook should be saved
213             * @throws IOException if the file could not be saved correctly
214             */
215            public void saveAs(Workbook workbook, File file) throws IOException {
216                    Codec codec = new CodecFactory().getCodec(file);
217                    if (codec != null) {
218                            FileOutputStream stream = null;
219                            try {
220                                    // Reads workbook data
221                                    stream = new FileOutputStream(file);
222                                    codec.write(workbook, stream);
223                            } finally {
224                                    try {
225                                            if (stream != null)
226                                                    stream.close();
227                                    } catch (IOException e) {}
228                            }
229    
230                            workbooks.put(workbook, file);
231                            fireSpreadsheetAppEvent(workbook, file, SpreadsheetAppEvent.Type.SAVED);
232                    }
233            }
234    
235            /**
236             * Returns the workbooks that are open.
237             * @return the workbooks that are open
238             */
239            public Workbook[] getWorkbooks() {
240                    Collection<Workbook> workbookSet = workbooks.keySet();
241                    return workbookSet.toArray(new Workbook[workbookSet.size()]);
242            }
243    
244            /**
245             * Returns the file in which the given workbook is stored.
246             * @return the file in which the given workbook is stored, or null if it isn't
247             */
248            public File getFile(Workbook workbook) {
249                    return workbooks.get(workbook);
250            }
251    
252            /**
253             * Returns whether a file has been specified for the given workbook,
254             * either when it was loaded or when it was last saved.
255             * @return whether the given workbook belongs to a file
256             */
257            public boolean isWorkbookStored(Workbook workbook) {
258                    return workbooks.get(workbook) != null;
259            }
260    
261            /**
262             * Returns the workbook that is stored in the given file, if it is already
263             * open.
264             * @param file the file to look for
265             * @return the workbook that is stored in the given file, or null if the file isn't open
266             */
267            public Workbook getWorkbook(File file) {
268                    for (Map.Entry<Workbook, File> entry : workbooks.entrySet())
269                            if (entry.getValue() != null && entry.getValue().equals(file))
270                                    return entry.getKey();
271                    return null;
272            }
273    
274            /**
275             * Returns whether the given file is open, and a workbook thereby loaded
276             * from it or saved to it.
277             * @param file the file to look for
278             * @return whether the given file is open
279             */
280            public boolean isFileOpen(File file) {
281                    return workbooks.containsValue(file);
282            }
283    
284            /**
285             * Registers the given listener on the spreadsheet application.
286             * @param listener the listener to be added
287             */
288            public void addSpreadsheetAppListener(SpreadsheetAppListener listener) {
289                    listeners.add(listener);
290            }
291    
292            /**
293             * Removes the given listener from the spreadsheet application.
294             * @param listener the listener to be removed
295             */
296            public void removeSpreadsheetAppListener(SpreadsheetAppListener listener) {
297                    listeners.remove(listener);
298            }
299    
300            /**
301             * Notifies all registered listeners that a spreadsheet application event
302             * occurred.
303             * @param workbook the workbook that was affected
304             * @param file the file that was affected
305             */
306            private void fireSpreadsheetAppEvent(Workbook workbook, File file,
307                            SpreadsheetAppEvent.Type type) {
308                    SpreadsheetAppEvent event
309                            = new SpreadsheetAppEvent(this, workbook, file, type);
310                    if (SwingUtilities.isEventDispatchThread())
311                            for (SpreadsheetAppListener listener : listeners)
312                                    switch (event.getType()) {
313                                            case CREATED:
314                                                    listener.workbookCreated(event); break;
315                                            case LOADED:
316                                                    listener.workbookLoaded(event); break;
317                                            case UNLOADED:
318                                                    listener.workbookUnloaded(event); break;
319                                            case SAVED:
320                                                    listener.workbookSaved(event); break;
321                                    }
322                    else
323                            SwingUtilities.invokeLater(
324                                    new EventDispatcher(event, 
325                                            listeners.toArray(new SpreadsheetAppListener[listeners.size()])
326                                    )
327                            );
328            }
329    
330            /**
331             * A utility for dispatching events on the AWT event dispatching thread.
332             * @author Einar Pehrson
333             */
334            public static class EventDispatcher implements Runnable {
335    
336                    /** The event to fire */
337                    private SpreadsheetAppEvent event;
338    
339                    /** The listeners to which the event should be dispatched */
340                    private SpreadsheetAppListener[] listeners;
341    
342                    /**
343                     * Creates a new event dispatcher.
344                     * @param event the event to fire
345                     * @param listeners the listeners to which the event should be dispatched
346                     */
347                    public EventDispatcher(SpreadsheetAppEvent event,
348                                    SpreadsheetAppListener[] listeners) {
349                            this.event = event;
350                            this.listeners = listeners;
351                    }
352    
353                    /**
354                     * Dispatches the event.
355                     */
356                    public void run() {
357                            for (SpreadsheetAppListener listener : listeners)
358                                    switch (event.getType()) {
359                                            case CREATED:
360                                                    listener.workbookCreated(event); break;
361                                            case LOADED:
362                                                    listener.workbookLoaded(event); break;
363                                            case UNLOADED:
364                                                    listener.workbookUnloaded(event); break;
365                                            case SAVED:
366                                                    listener.workbookSaved(event); break;
367                                    }
368                    }
369            }
370    }