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 */
021package csheets;
022
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileNotFoundException;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.text.DateFormat;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.Date;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.Properties;
037
038import javax.swing.SwingUtilities;
039
040import csheets.core.Workbook;
041import csheets.core.formula.compiler.FormulaCompiler;
042import csheets.core.formula.lang.Language;
043import csheets.ext.ExtensionManager;
044import csheets.io.Codec;
045import csheets.io.CodecFactory;
046import 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 */
054public 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                
116                try {
117                        javax.swing.UIManager.setLookAndFeel(
118                                javax.swing.UIManager.getCrossPlatformLookAndFeelClassName());
119                } catch (Exception e) {
120                }
121                /* try {
122                        javax.swing.UIManager.setLookAndFeel("className");
123                } catch (Exception e) {} */
124
125                // Creates user interface
126                new csheets.ui.Frame.Creator(app).createAndWait();
127                app.create();
128        }
129
130        /**
131         * Returns the current user properties.
132         * @return the current user properties
133         */
134        public Properties getUserProperties() {
135                return props;
136        }
137
138        /**
139         * Exits the application.
140         */
141        public void exit() {
142                // Stores properties
143                if (props.size() > 0)
144                        try {
145                                props.storeToXML("CleanSheets User Properties (" + 
146                                        DateFormat.getDateTimeInstance().format(new Date()) + ")");
147                        } catch (IOException e) {
148                                System.err.println("An error occurred while saving properties.");
149                        }
150
151                // Terminates the virtual machine
152                System.exit(0);
153        }
154
155        /**
156         * Creates a new workbook.
157         */
158        public void create() {
159                Workbook workbook = new Workbook(3);
160                workbooks.put(workbook, null);
161                fireSpreadsheetAppEvent(workbook, null, SpreadsheetAppEvent.Type.CREATED);
162        }
163
164        /**
165         * Loads a workbook from the given file.
166         * @param file the file in which the workbook is stored
167         * @throws IOException if the file could not be loaded correctly
168         */
169        public void load(File file) throws IOException, ClassNotFoundException {
170                Codec codec = new CodecFactory().getCodec(file);
171                if (codec != null) {
172                        FileInputStream stream = null;
173                        Workbook workbook;
174                        try {
175                                // Reads workbook data
176                                stream = new FileInputStream(file);
177                                workbook = codec.read(stream);
178                        } finally {
179                                try {
180                                        if (stream != null)
181                                                stream.close();
182                                } catch (IOException e) {}
183                        }
184
185                        // Loads the workbook
186                        workbooks.put(workbook, file);
187                        fireSpreadsheetAppEvent(workbook, file, SpreadsheetAppEvent.Type.LOADED);
188                } else
189                        throw new IOException("Codec could not be found");
190        }
191
192        /**
193         * Unloads the given workbook.
194         * @param workbook the workbook to unload
195         */
196        public void unload(Workbook workbook) {
197                File file = workbooks.remove(workbook);
198                fireSpreadsheetAppEvent(workbook, file, SpreadsheetAppEvent.Type.UNLOADED);
199        }
200
201        /**
202         * Saves the given workbook to the file from which it was loaded,
203         * or to which it was most recently saved.
204         * @param workbook the workbook to save
205         * @throws IOException if the file could not be saved correctly
206         */
207        public void save(Workbook workbook) throws IOException {
208                File file = workbooks.get(workbook);
209                if (file != null)
210                        saveAs(workbook, file);
211                else
212                        throw new FileNotFoundException("No file assigned to the workbook.");
213        }
214
215        /**
216         * Saves the given workbook to the given file.
217         * @param workbook the workbook to save
218         * @param file the file to which the workbook should be saved
219         * @throws IOException if the file could not be saved correctly
220         */
221        public void saveAs(Workbook workbook, File file) throws IOException {
222                Codec codec = new CodecFactory().getCodec(file);
223                if (codec != null) {
224                        FileOutputStream stream = null;
225                        try {
226                                // Reads workbook data
227                                stream = new FileOutputStream(file);
228                                codec.write(workbook, stream);
229                        } finally {
230                                try {
231                                        if (stream != null)
232                                                stream.close();
233                                } catch (IOException e) {}
234                        }
235
236                        workbooks.put(workbook, file);
237                        fireSpreadsheetAppEvent(workbook, file, SpreadsheetAppEvent.Type.SAVED);
238                }
239        }
240
241        /**
242         * Returns the workbooks that are open.
243         * @return the workbooks that are open
244         */
245        public Workbook[] getWorkbooks() {
246                Collection<Workbook> workbookSet = workbooks.keySet();
247                return workbookSet.toArray(new Workbook[workbookSet.size()]);
248        }
249
250        /**
251         * Returns the file in which the given workbook is stored.
252         * @return the file in which the given workbook is stored, or null if it isn't
253         */
254        public File getFile(Workbook workbook) {
255                return workbooks.get(workbook);
256        }
257
258        /**
259         * Returns whether a file has been specified for the given workbook,
260         * either when it was loaded or when it was last saved.
261         * @return whether the given workbook belongs to a file
262         */
263        public boolean isWorkbookStored(Workbook workbook) {
264                return workbooks.get(workbook) != null;
265        }
266
267        /**
268         * Returns the workbook that is stored in the given file, if it is already
269         * open.
270         * @param file the file to look for
271         * @return the workbook that is stored in the given file, or null if the file isn't open
272         */
273        public Workbook getWorkbook(File file) {
274                for (Map.Entry<Workbook, File> entry : workbooks.entrySet())
275                        if (entry.getValue() != null && entry.getValue().equals(file))
276                                return entry.getKey();
277                return null;
278        }
279
280        /**
281         * Returns whether the given file is open, and a workbook thereby loaded
282         * from it or saved to it.
283         * @param file the file to look for
284         * @return whether the given file is open
285         */
286        public boolean isFileOpen(File file) {
287                return workbooks.containsValue(file);
288        }
289
290        /**
291         * Registers the given listener on the spreadsheet application.
292         * @param listener the listener to be added
293         */
294        public void addSpreadsheetAppListener(SpreadsheetAppListener listener) {
295                listeners.add(listener);
296        }
297
298        /**
299         * Removes the given listener from the spreadsheet application.
300         * @param listener the listener to be removed
301         */
302        public void removeSpreadsheetAppListener(SpreadsheetAppListener listener) {
303                listeners.remove(listener);
304        }
305
306        /**
307         * Notifies all registered listeners that a spreadsheet application event
308         * occurred.
309         * @param workbook the workbook that was affected
310         * @param file the file that was affected
311         */
312        private void fireSpreadsheetAppEvent(Workbook workbook, File file,
313                        SpreadsheetAppEvent.Type type) {
314                SpreadsheetAppEvent event
315                        = new SpreadsheetAppEvent(this, workbook, file, type);
316                if (SwingUtilities.isEventDispatchThread())
317                        for (SpreadsheetAppListener listener : listeners)
318                                switch (event.getType()) {
319                                        case CREATED:
320                                                listener.workbookCreated(event); break;
321                                        case LOADED:
322                                                listener.workbookLoaded(event); break;
323                                        case UNLOADED:
324                                                listener.workbookUnloaded(event); break;
325                                        case SAVED:
326                                                listener.workbookSaved(event); break;
327                                }
328                else
329                        SwingUtilities.invokeLater(
330                                new EventDispatcher(event, 
331                                        listeners.toArray(new SpreadsheetAppListener[listeners.size()])
332                                )
333                        );
334        }
335
336        /**
337         * A utility for dispatching events on the AWT event dispatching thread.
338         * @author Einar Pehrson
339         */
340        public static class EventDispatcher implements Runnable {
341
342                /** The event to fire */
343                private SpreadsheetAppEvent event;
344
345                /** The listeners to which the event should be dispatched */
346                private SpreadsheetAppListener[] listeners;
347
348                /**
349                 * Creates a new event dispatcher.
350                 * @param event the event to fire
351                 * @param listeners the listeners to which the event should be dispatched
352                 */
353                public EventDispatcher(SpreadsheetAppEvent event,
354                                SpreadsheetAppListener[] listeners) {
355                        this.event = event;
356                        this.listeners = listeners;
357                }
358
359                /**
360                 * Dispatches the event.
361                 */
362                public void run() {
363                        for (SpreadsheetAppListener listener : listeners)
364                                switch (event.getType()) {
365                                        case CREATED:
366                                                listener.workbookCreated(event); break;
367                                        case LOADED:
368                                                listener.workbookLoaded(event); break;
369                                        case UNLOADED:
370                                                listener.workbookUnloaded(event); break;
371                                        case SAVED:
372                                                listener.workbookSaved(event); break;
373                                }
374                }
375        }
376}