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}