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.ui.ctrl;
022
023 import java.util.ArrayList;
024 import java.util.EmptyStackException;
025 import java.util.HashMap;
026 import java.util.LinkedList;
027 import java.util.List;
028 import java.util.Map;
029 import java.util.Properties;
030 import java.util.Stack;
031
032 import javax.swing.SwingUtilities;
033 import javax.swing.TransferHandler;
034
035 import csheets.CleanSheets;
036 import csheets.SpreadsheetAppEvent;
037 import csheets.SpreadsheetAppListener;
038 import csheets.core.Cell;
039 import csheets.core.Spreadsheet;
040 import csheets.core.Workbook;
041 import csheets.ext.Extension;
042 import csheets.ext.ExtensionManager;
043 import csheets.ui.ext.UIExtension;
044 import 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 */
052 public 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 }