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    }