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 }