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.ui.ctrl; 022 023import java.util.ArrayList; 024import java.util.EmptyStackException; 025import java.util.HashMap; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Properties; 030import java.util.Stack; 031 032import javax.swing.SwingUtilities; 033import javax.swing.TransferHandler; 034 035import csheets.CleanSheets; 036import csheets.SpreadsheetAppEvent; 037import csheets.SpreadsheetAppListener; 038import csheets.core.Cell; 039import csheets.core.Spreadsheet; 040import csheets.core.Workbook; 041import csheets.ext.Extension; 042import csheets.ext.ExtensionManager; 043import csheets.ui.ext.UIExtension; 044import csheets.ui.sheet.CellTransferHandler; 045 046/** 047 * A controller for managing the current selection, i.e. the active workbook, 048 * spreadsheet and cell, as well as for keeping track of modifications to 049 * workbooks and of user interface extensions. 050 * @author Einar Pehrson 051 */ 052public class UIController implements SpreadsheetAppListener { 053 054 /** The active workbook */ 055 private Workbook activeWorkbook; 056 057 /** The active spreadsheet */ 058 private Spreadsheet activeSpreadsheet; 059 060 /** The active cell */ 061 private Cell activeCell; 062 063 /** The workbooks that have been selected, in order */ 064 private Stack<Workbook> workbooks = new Stack<Workbook>(); 065 066 /** The map that registers whether workbooks have changes */ 067 private Map<Workbook, Boolean> changeLog = new HashMap<Workbook, Boolean>(); 068 069 /** The CleanSheets application */ 070 private CleanSheets app; 071 072 /** The transfer haandler used to transfer ranges of cells */ 073 private TransferHandler transferHandler = new CellTransferHandler(); 074 075 /** The user interface extensions that have been loaded */ 076 private UIExtension[] extensions; 077 078 /** The selection listeners registered to receive events */ 079 private List<SelectionListener> selListeners = new ArrayList<SelectionListener>(); 080 081 /** The edit listeners registered to receive events */ 082 private List<EditListener> editListeners = new ArrayList<EditListener>(); 083 084 // private Map<Workbook, Spreadsheet> activeSpreadsheets; 085 // private Map<Spreadsheet, Cell> activeCells; 086 087 /** 088 * Creates a new user interface controller. 089 * @param app the CleanSheets application 090 */ 091 public UIController(CleanSheets app) { 092 // Stores members 093 this.app = app; 094 app.addSpreadsheetAppListener(this); 095 096 // Fetches extensions 097 List<UIExtension> uiExtensions = new LinkedList<UIExtension>(); 098 for (Extension extension : ExtensionManager.getInstance().getExtensions()) { 099 UIExtension uiExtension = extension.getUIExtension(this); 100 if (uiExtension != null) 101 uiExtensions.add(uiExtension); 102 } 103 this.extensions = 104 uiExtensions.toArray(new UIExtension[uiExtensions.size()]); 105 } 106 107/* 108 * SELECTION 109 */ 110 111 /** 112 * Returns the active workbook. 113 * @return the active workbook 114 */ 115 public Workbook getActiveWorkbook() { 116 return activeWorkbook; 117 } 118 119 /** 120 * Sets the given workbook of the application. 121 * @param workbook the workbook to use 122 */ 123 public void setActiveWorkbook(Workbook workbook) { 124 if (activeWorkbook == null || activeWorkbook != workbook) { 125 Workbook prevWorkbook = activeWorkbook; 126 Spreadsheet prevSpreadsheet = activeSpreadsheet; 127 Cell prevCell = activeCell; 128 activeWorkbook = workbook; 129 activeSpreadsheet = null; 130 activeCell = null; 131 if (activeWorkbook != null) { 132 workbooks.remove(activeWorkbook); 133 workbooks.push(activeWorkbook); 134 } 135 fireSelectionChanged(new SelectionEvent(this, 136 activeWorkbook, activeSpreadsheet, activeCell, 137 prevWorkbook, prevSpreadsheet, prevCell)); 138 } 139 } 140 141 /** 142 * Returns the active spreadsheet. 143 * @return the active spreadsheet 144 */ 145 public Spreadsheet getActiveSpreadsheet() { 146 return activeSpreadsheet; 147 } 148 149 /** 150 * Sets the active spreadsheet of the application, and thereby also the 151 * active workbook. 152 * @param spreadsheet the spreadsheet to use 153 */ 154 public void setActiveSpreadsheet(Spreadsheet spreadsheet) { 155 if (activeSpreadsheet == null || activeSpreadsheet != spreadsheet) { 156 Workbook prevWorkbook = activeWorkbook; 157 Spreadsheet prevSpreadsheet = activeSpreadsheet; 158 Cell prevCell = activeCell; 159 activeSpreadsheet = spreadsheet; 160 activeWorkbook = activeSpreadsheet.getWorkbook(); 161 if (activeWorkbook != null) { 162 workbooks.remove(activeWorkbook); 163 workbooks.push(activeWorkbook); 164 } 165 fireSelectionChanged(new SelectionEvent(this, 166 activeWorkbook, activeSpreadsheet, activeCell, 167 prevWorkbook, prevSpreadsheet, prevCell)); 168 } 169 } 170 171 /** 172 * Returns the active cell of the active workbook's active spreadsheet. 173 * @return the active cell 174 */ 175 public Cell getActiveCell() { 176 return activeCell; 177 } 178 179 /** 180 * Sets the active cell of the application, and thereby also the active 181 * spreadsheet and workbook. 182 * @param cell the cell to use 183 */ 184 public void setActiveCell(Cell cell) { 185 if (activeCell == null || activeCell != cell) { 186 Workbook prevWorkbook = activeWorkbook; 187 Spreadsheet prevSpreadsheet = activeSpreadsheet; 188 Cell prevCell = activeCell; 189 activeCell = cell; 190 activeSpreadsheet = cell.getSpreadsheet(); 191 activeWorkbook = activeSpreadsheet.getWorkbook(); 192 if (activeWorkbook != null) { 193 workbooks.remove(activeWorkbook); 194 workbooks.push(activeWorkbook); 195 } 196 fireSelectionChanged(new SelectionEvent(this, 197 activeWorkbook, activeSpreadsheet, activeCell, 198 prevWorkbook, prevSpreadsheet, prevCell)); 199 } 200 } 201 202/* 203 * EDITING 204 */ 205 206 /** 207 * Returns whether the active workbook has been modified. 208 * @return whether the active workbook has been modified 209 */ 210 public boolean isActiveWorkbookModified() { 211 if (activeWorkbook != null) { 212 Boolean modified = changeLog.get(activeWorkbook); 213 return modified != null && modified == true; 214 } else 215 return false; 216 } 217 218 /** 219 * Returns whether the given workbook has been modified. 220 * @return whether the given workbook has been modified 221 */ 222 public boolean isWorkbookModified(Workbook workbook) { 223 Boolean modified = changeLog.get(workbook); 224 return modified != null && modified == true; 225 } 226 227 /** 228 * Specifies whether the given workbook has been modified. 229 * @param workbook the relevant workbook 230 */ 231 public void setWorkbookModified(Workbook workbook) { 232 changeLog.put(workbook, true); 233 fireWorkbookModified(workbook); 234 } 235 236 /** 237 * Returns the transfer haandler used to transfer ranges of cells. 238 * @return the transfer haandler used to transfer ranges of cells 239 */ 240 public TransferHandler getCellTransferHandler() { 241 return transferHandler; 242 } 243 244/* 245 * PROPERTIES 246 */ 247 248 /** 249 * Returns the current user properties. 250 * @return the current user properties 251 */ 252 public Properties getUserProperties() { 253 return app.getUserProperties(); 254 } 255 256/* 257 * EXTENSIONS 258 */ 259 260 /** 261 * Returns the user interface extensions that have been loaded. 262 * @return the user interface extensions that have been loaded 263 */ 264 public UIExtension[] getExtensions() { 265 return extensions; 266 } 267 268/* 269 * EVENT FIRING & LISTENING 270 */ 271 272 public void workbookCreated(SpreadsheetAppEvent event) { 273 Workbook workbook = event.getWorkbook(); 274 changeLog.put(workbook, false); 275 if (workbook.getSpreadsheetCount() > 0) 276 setActiveCell(workbook.getSpreadsheet(0).getCell(0, 0)); 277 else 278 setActiveWorkbook(workbook); 279 } 280 281 public void workbookLoaded(SpreadsheetAppEvent event) { 282 workbookCreated(event); 283 } 284 285 public void workbookUnloaded(SpreadsheetAppEvent event) { 286 changeLog.remove(event.getWorkbook()); 287 workbooks.remove(event.getWorkbook()); 288 Workbook activeWorkbook = null; 289 try { 290 activeWorkbook = workbooks.peek(); 291 } catch (EmptyStackException e) {} 292 setActiveWorkbook(activeWorkbook); 293 } 294 295 public void workbookSaved(SpreadsheetAppEvent event) { 296 changeLog.put(event.getWorkbook(), false); 297 } 298 299 /** 300 * Registers the given listener on the user interface controller. 301 * @param listener the listener to be added 302 */ 303 public void addSelectionListener(SelectionListener listener) { 304 selListeners.add(listener); 305 } 306 307 /** 308 * Removes the given listener from the user interface controller. 309 * @param listener the listener to be removed 310 */ 311 public void removeSelectionListener(SelectionListener listener) { 312 selListeners.remove(listener); 313 } 314 315 /** 316 * Registers the given listener on the user interface controller. 317 * @param listener the listener to be added 318 */ 319 public void addEditListener(EditListener listener) { 320 editListeners.add(listener); 321 } 322 323 /** 324 * Removes the given listener from the user interface controller. 325 * @param listener the listener to be removed 326 */ 327 public void removeEditListener(EditListener listener) { 328 editListeners.remove(listener); 329 } 330 331 /** 332 * Notifies all registered listeners that the selection changed. 333 * @param event the event to fire 334 */ 335 private void fireSelectionChanged(SelectionEvent event) { 336 SwingUtilities.invokeLater(new EventDispatcher(event, 337 selListeners.toArray(new SelectionListener[selListeners.size()]))); 338 } 339 340 /** 341 * Notifies all registered listeners that the workbook was modified. 342 * @param workbook the workbook that was modified 343 */ 344 private void fireWorkbookModified(Workbook workbook) { 345 EditEvent event = new EditEvent(this, workbook); 346 for (EditListener listener : editListeners.toArray( 347 new EditListener[editListeners.size()])) 348 listener.workbookModified(event); 349 } 350 351 /** 352 * A utility for dispatching events on the AWT event dispatching thread. 353 * @author Einar Pehrson 354 */ 355 public static class EventDispatcher implements Runnable { 356 357 /** The event to fire */ 358 private SelectionEvent event; 359 360 /** The listeners to which the event should be dispatched */ 361 private SelectionListener[] listeners; 362 363 /** 364 * Creates a new event dispatcher. 365 * @param event the event to fire 366 * @param listeners the listeners to which the event should be dispatched 367 */ 368 public EventDispatcher(SelectionEvent event, SelectionListener[] listeners) { 369 this.event = event; 370 this.listeners = listeners; 371 } 372 373 /** 374 * Dispatches the event. 375 */ 376 public void run() { 377 for (SelectionListener listener : listeners) 378 listener.selectionChanged(event); 379 } 380 } 381}