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}