Xcos: fix recent diagrams management
[scilab.git] / scilab / modules / graph / src / java / org / scilab / modules / graph / ScilabGraph.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009 - DIGITEO - Bruno JOFRET
4  * Copyright (C) 2010 - DIGITEO - Clement DAVID
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16
17 package org.scilab.modules.graph;
18
19 import java.beans.PropertyChangeEvent;
20 import java.beans.PropertyChangeListener;
21 import java.io.File;
22 import java.text.DateFormat;
23 import java.util.ArrayList;
24 import java.util.Calendar;
25 import java.util.Date;
26 import java.util.Iterator;
27 import java.util.List;
28
29 import org.scilab.modules.graph.utils.ScilabGraphConstants;
30 import org.scilab.modules.graph.utils.ScilabGraphMessages;
31 import org.scilab.modules.graph.view.ScilabGraphView;
32
33 import com.mxgraph.model.mxGraphModel;
34 import com.mxgraph.model.mxGraphModel.mxChildChange;
35 import com.mxgraph.model.mxGraphModel.mxCollapseChange;
36 import com.mxgraph.model.mxGraphModel.mxGeometryChange;
37 import com.mxgraph.model.mxGraphModel.mxStyleChange;
38 import com.mxgraph.model.mxGraphModel.mxTerminalChange;
39 import com.mxgraph.model.mxGraphModel.mxValueChange;
40 import com.mxgraph.model.mxGraphModel.mxVisibleChange;
41 import com.mxgraph.model.mxIGraphModel;
42 import com.mxgraph.swing.mxGraphComponent;
43 import com.mxgraph.swing.handler.mxRubberband;
44 import com.mxgraph.swing.util.mxGraphActions;
45 import com.mxgraph.util.mxEvent;
46 import com.mxgraph.util.mxEventObject;
47 import com.mxgraph.util.mxUndoManager;
48 import com.mxgraph.util.mxUndoableEdit;
49 import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
50 import com.mxgraph.view.mxGraph;
51 import com.mxgraph.view.mxGraphView;
52 import com.mxgraph.view.mxStylesheet;
53
54 /**
55  * Represent the base diagram of Xcos.
56  *
57  * It performs generic operations like undo/redo management, action clean-up,
58  * modification state management, SwingScilabTab association, etc...
59  */
60 public class ScilabGraph extends mxGraph {
61     /**
62      * The default component of a scilab graph
63      */
64     private ScilabComponent component;
65
66     private final mxUndoManager undoManager = new mxUndoManager();
67
68     private String title = null;
69     private File savedFile;
70     private boolean modified;
71     private boolean opened;
72     private boolean readOnly;
73
74     private transient mxRubberband rubberBand;
75
76     private transient String graphTab;
77     private transient String viewPortTab;
78
79     /**
80      * Manage the modification state on change
81      */
82     private final mxIEventListener changeTracker = new mxIEventListener() {
83         @Override
84         public void invoke(Object source, mxEventObject evt) {
85             setModified(true);
86         }
87     };
88
89     /**
90      * Manage the undo/redo on change
91      */
92     private final mxIEventListener undoHandler = new mxIEventListener() {
93         @Override
94         public void invoke(Object source, mxEventObject evt) {
95             undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty(ScilabGraphConstants.EVENT_CHANGE_EDIT));
96         }
97     };
98
99     /**
100      * Remove the undo handler from the component
101      */
102     public void removeUndoHandler() {
103         getModel().removeListener(undoHandler, mxEvent.UNDO);
104     }
105
106     /**
107      * Register the undo handler on the right component
108      */
109     public void registerUndoHandler() {
110         // Undo / Redo capabilities
111         getModel().addListener(mxEvent.UNDO, undoHandler);
112     }
113
114     /**
115      * Update the selection on undo/redo
116      */
117     private final mxIEventListener selectionHandler = new mxIEventListener() {
118         @Override
119         public void invoke(Object source, mxEventObject evt) {
120             List<mxUndoableChange> changes = ((mxUndoableEdit) evt.getProperty(ScilabGraphConstants.EVENT_CHANGE_EDIT)).getChanges();
121             getSelectionModel().setCells(getSelectionCellsForChanges(changes));
122         }
123     };
124
125     /**
126      * Update the component when the graph is locked
127      */
128     private final PropertyChangeListener cellLockBackgroundUpdater = new PropertyChangeListener() {
129         @Override
130         public void propertyChange(PropertyChangeEvent evt) {
131             if (evt.getPropertyName().equals("cellsLocked")) {
132                 getAsComponent().getGraphControl().repaint();
133             }
134         }
135     };
136
137     /**
138      * Default constructor:
139      * <UL>
140      *   <LI> disable unused actions
141      *   <LI> install listeners
142      *   <LI> Replace JGraphX components by specialized components if needed.
143      */
144     public ScilabGraph() {
145         this(null, null);
146     }
147
148     /**
149      * Constructor using a specific model
150      * @param model the model to use
151      */
152     public ScilabGraph(mxIGraphModel model, mxStylesheet stylesheet) {
153         super(model, stylesheet);
154
155         // Disabling the default connected action and event listeners.
156         mxGraphActions.getSelectNextAction().setEnabled(false);
157         mxGraphActions.getSelectPreviousAction().setEnabled(false);
158         mxGraphActions.getSelectChildAction().setEnabled(false);
159         mxGraphActions.getSelectParentAction().setEnabled(false);
160
161         registerUndoHandler();
162
163         // Keeps the selection in sync with the command history
164         undoManager.addListener(mxEvent.UNDO, selectionHandler);
165         undoManager.addListener(mxEvent.REDO, selectionHandler);
166
167         setComponent(new ScilabComponent(this));
168
169         // graph locked change support
170         changeSupport.addPropertyChangeListener(cellLockBackgroundUpdater);
171
172         // Modified property change
173         getModel().addListener(mxEvent.CHANGE, changeTracker);
174     }
175
176
177
178     /**
179      * @return The previously saved file or null.
180      */
181     public File getSavedFile() {
182         return savedFile;
183     }
184
185     /**
186      * @param savedFile
187      *            The new saved file
188      */
189     public void setSavedFile(final File savedFile) {
190         this.savedFile = savedFile;
191
192         // register the saved dir as the image base path (for relative images
193         // location).
194         if (savedFile != null && savedFile.getParentFile() != null) {
195             getAsComponent().getCanvas().setImageBasePath(savedFile.getParentFile().toURI().toASCIIString());
196         }
197     }
198
199     /**
200      * @return true, if the graph has been modified ; false otherwise.
201      */
202     public boolean isModified() {
203         return modified;
204     }
205
206     /**
207      * Modify the state of the diagram.
208      *
209      * @param modified
210      *            The new modified state.
211      * @category UseEvent
212      */
213     public void setModified(boolean modified) {
214         boolean oldValue = this.modified;
215         this.modified = modified;
216
217         if (getAsComponent() != null) {
218             getAsComponent().firePropertyChange("modified", oldValue, modified);
219         }
220     }
221
222     /**
223      * @param title
224      *            The new title of the tab
225      */
226     public void setTitle(String title) {
227         this.title = title;
228     }
229
230     /**
231      * @return The current Tab title
232      */
233     public String getTitle() {
234         if (title == null) {
235             final Date d = Calendar.getInstance().getTime();
236             final String time = DateFormat.getTimeInstance().format(d);
237             title = String.format(ScilabGraphMessages.UNTITLED, time);
238         }
239         return title;
240     }
241
242     /**
243      * Get the graph tab uuid
244      *
245      * @return
246      */
247     public String getGraphTab() {
248         return graphTab;
249     }
250
251     /**
252      * Set the graph tab uuid
253      *
254      * @param uuid
255      *            the diagram tab
256      */
257     public void setGraphTab(String uuid) {
258         this.graphTab = uuid;
259     }
260
261     /**
262      * Get the view port tab uuid
263      *
264      * @return the view port tab
265      */
266     public String getViewPortTab() {
267         return viewPortTab;
268     }
269
270     /**
271      * Set the view port tab uuid
272      *
273      * @param uuid
274      *            the view port tab
275      */
276     public void setViewPortTab(String uuid) {
277         this.viewPortTab = uuid;
278     }
279
280     /**
281      * @return The component associated with the current graph.
282      */
283     public mxGraphComponent getAsComponent() {
284         return component;
285     }
286
287     /**
288      * @param component
289      *            The graphical component associated with this graph
290      */
291     public void setComponent(ScilabComponent component) {
292         this.component = component;
293
294         if (component != null) {
295             // Adds rubberband selection
296             rubberBand = new mxRubberband(component);
297         } else {
298             rubberBand = null;
299         }
300     }
301
302     /**
303      * A read-only state will disable all actions in the graph.
304      *
305      * @param readOnly
306      *            Read-only state
307      */
308     public void setReadOnly(boolean readOnly) {
309         this.readOnly = readOnly;
310
311         setCellsLocked(readOnly);
312     }
313
314     /**
315      * @return True if actions are not allowed, false otherwise.
316      */
317     public boolean isReadonly() {
318         return readOnly;
319     }
320
321     /**
322      * @return The associated RubberBand
323      * @see com.mxgraph.swing.handler.mxRubberband
324      */
325     public mxRubberband getRubberBand() {
326         return rubberBand;
327     }
328
329     /**
330      * @return The undo manager associated with this graph
331      */
332     public final mxUndoManager getUndoManager() {
333         return undoManager;
334     }
335
336     /**
337      * @return the newly allocated graph
338      * @see com.mxgraph.view.mxGraph#createGraphView()
339      */
340     @Override
341     protected mxGraphView createGraphView() {
342         return new ScilabGraphView(this);
343     }
344
345     /*
346      * Utils
347      */
348     /**
349      * Returns the cells to be selected for the given list of changes.
350      *
351      * @param changes
352      *            the changes
353      * @param model
354      *            the model to work on
355      * @return the cells
356      */
357     public static Object[] getSelectionCellsForChanges(final List<mxUndoableChange> changes, final mxGraphModel model) {
358         List<Object> cells = new ArrayList<Object>();
359         Iterator<mxUndoableChange> it = changes.iterator();
360
361         while (it.hasNext()) {
362             Object change = it.next();
363
364             if (change instanceof mxChildChange) {
365                 cells.add(((mxChildChange) change).getChild());
366             } else if (change instanceof mxTerminalChange) {
367                 cells.add(((mxTerminalChange) change).getCell());
368             } else if (change instanceof mxValueChange) {
369                 cells.add(((mxValueChange) change).getCell());
370             } else if (change instanceof mxStyleChange) {
371                 cells.add(((mxStyleChange) change).getCell());
372             } else if (change instanceof mxGeometryChange) {
373                 cells.add(((mxGeometryChange) change).getCell());
374             } else if (change instanceof mxCollapseChange) {
375                 cells.add(((mxCollapseChange) change).getCell());
376             } else if (change instanceof mxVisibleChange) {
377                 mxVisibleChange vc = (mxVisibleChange) change;
378
379                 if (vc.isVisible()) {
380                     cells.add(((mxVisibleChange) change).getCell());
381                 }
382             }
383         }
384
385         return mxGraphModel.getTopmostCells(model, cells.toArray());
386     }
387 }