Line | Hits | Source |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2003, the JUNG Project and the Regents of the University | |
3 | * of California | |
4 | * All rights reserved. | |
5 | * | |
6 | * This software is open-source under the BSD license; see either | |
7 | * "license.txt" or | |
8 | * http://jung.sourceforge.net/license.txt for a description. | |
9 | */ | |
10 | package edu.uci.ics.jung.visualization; | |
11 | ||
12 | import java.awt.Dimension; | |
13 | ||
14 | import javax.swing.event.ChangeEvent; | |
15 | import javax.swing.event.ChangeListener; | |
16 | import javax.swing.event.EventListenerList; | |
17 | ||
18 | import edu.uci.ics.jung.utils.ChangeEventSupport; | |
19 | import edu.uci.ics.jung.utils.DefaultChangeEventSupport; | |
20 | ||
21 | /** | |
22 | * The model containing state values for | |
23 | * visualizations of graphs. | |
24 | * Refactored and extracted from the 1.6.0 version of VisualizationViewer | |
25 | * | |
26 | * @author Tom Nelson | |
27 | */ | |
28 | public class DefaultVisualizationModel implements VisualizationModel, ChangeEventSupport { | |
29 | ||
30 | 0 | ChangeEventSupport changeSupport = |
31 | new DefaultChangeEventSupport(this); | |
32 | ||
33 | /** | |
34 | * a callback called during relaxer iteration | |
35 | */ | |
36 | protected StatusCallback statusCallback; | |
37 | ||
38 | /** | |
39 | * the thread that applies the current layout algorithm | |
40 | */ | |
41 | Thread relaxer; | |
42 | ||
43 | /** | |
44 | * when <code>true</code>, the relaxer thread will enter a wait state | |
45 | * until unsuspend is called | |
46 | */ | |
47 | boolean manualSuspend; | |
48 | ||
49 | /** | |
50 | * the layout algorithm currently in use | |
51 | */ | |
52 | protected Layout layout; | |
53 | ||
54 | /** | |
55 | * how long the relaxer thread pauses between iteration loops. | |
56 | */ | |
57 | 0 | protected long relaxerThreadSleepTime = 100L; |
58 | ||
59 | protected ChangeListener changeListener; | |
60 | ||
61 | /** | |
62 | * | |
63 | * @param layout The Layout to apply, with its associated Graph | |
64 | */ | |
65 | public DefaultVisualizationModel(Layout layout) { | |
66 | 0 | this(layout, null); |
67 | 0 | } |
68 | ||
69 | /** | |
70 | * | |
71 | * @param layout | |
72 | * @param d The preferred size of the View that will display this graph | |
73 | */ | |
74 | 0 | public DefaultVisualizationModel(Layout layout, Dimension d) { |
75 | 0 | if(changeListener == null) { |
76 | 0 | changeListener = new ChangeListener() { |
77 | public void stateChanged(ChangeEvent e) { | |
78 | fireStateChanged(); | |
79 | } | |
80 | }; | |
81 | } | |
82 | 0 | setGraphLayout(layout, d); |
83 | 0 | init(); |
84 | 0 | } |
85 | ||
86 | /** | |
87 | * Returns the time between iterations of the | |
88 | * Relaxer thread. The Relaxer thread sleeps for | |
89 | * a moment before calling the Layout to update | |
90 | * again. This tells | |
91 | * how long the current delay is. | |
92 | * The default, 20 milliseconds, essentially | |
93 | * causes the system to run the next iteration | |
94 | * with virtually no pause. | |
95 | * @return Returns the relaxerThreadSleepTime. | |
96 | */ | |
97 | public long getRelaxerThreadSleepTime() { | |
98 | 0 | return relaxerThreadSleepTime; |
99 | } | |
100 | ||
101 | /** | |
102 | * Sets the relaxerThreadSleepTime. | |
103 | * @see #getRelaxerThreadSleepTime() | |
104 | * @param relaxerThreadSleepTime The relaxerThreadSleepTime to set. | |
105 | */ | |
106 | public void setRelaxerThreadSleepTime(long relaxerThreadSleepTime) { | |
107 | 0 | this.relaxerThreadSleepTime = relaxerThreadSleepTime; |
108 | 0 | } |
109 | ||
110 | /** | |
111 | * Removes the current graph layout, and adds a new one. | |
112 | * @param layout the new layout to use | |
113 | * @param viewSize the size of the View that will display this layout | |
114 | */ | |
115 | public void setGraphLayout(Layout layout, Dimension viewSize) { | |
116 | 0 | if(this.layout != null && this.layout instanceof ChangeEventSupport) { |
117 | 0 | ((ChangeEventSupport)this.layout).removeChangeListener(changeListener); |
118 | } | |
119 | 0 | if(viewSize == null) { |
120 | 0 | viewSize = new Dimension(600,600); |
121 | } | |
122 | 0 | suspend(); |
123 | 0 | Dimension layoutSize = layout.getCurrentSize(); |
124 | // if the layout has NOT been initialized yet, initialize it | |
125 | // now to the size of the VisualizationViewer window | |
126 | 0 | if(layoutSize == null) { |
127 | 0 | layout.initialize(viewSize); |
128 | } else { | |
129 | 0 | layout.restart(); |
130 | } | |
131 | 0 | layoutSize = layout.getCurrentSize(); |
132 | ||
133 | 0 | this.layout = layout; |
134 | 0 | if(this.layout instanceof ChangeEventSupport) { |
135 | 0 | ((ChangeEventSupport)this.layout).addChangeListener(changeListener); |
136 | } | |
137 | 0 | prerelax(); |
138 | 0 | unsuspend(); |
139 | 0 | } |
140 | ||
141 | /** | |
142 | * set the graph Layout and if it is not already initialized, initialize | |
143 | * it to the default VisualizationViewer preferred size of 600x600 | |
144 | */ | |
145 | public void setGraphLayout(Layout layout) { | |
146 | 0 | setGraphLayout(layout, null); |
147 | 0 | } |
148 | ||
149 | /** | |
150 | * Returns the current graph layout. | |
151 | */ | |
152 | public Layout getGraphLayout() { | |
153 | 0 | return layout; |
154 | } | |
155 | ||
156 | /** | |
157 | * starts a visRunner thread without prerelaxing | |
158 | */ | |
159 | public synchronized void restartThreadOnly() { | |
160 | 0 | if (visRunnerIsRunning ) { |
161 | 0 | stop(); |
162 | //throw new FatalException("Can't init while a visrunner is running"); | |
163 | } | |
164 | 0 | relaxer = new VisRunner(); |
165 | 0 | relaxer.setPriority(Thread.MIN_PRIORITY); |
166 | 0 | relaxer.start(); |
167 | 0 | } |
168 | ||
169 | /** | |
170 | * Pre-relaxes and starts a visRunner thread | |
171 | */ | |
172 | public synchronized void init() { | |
173 | 0 | if (visRunnerIsRunning ) { |
174 | 0 | stop(); |
175 | // throw new FatalException("Can't init while a visrunner is running"); | |
176 | } | |
177 | 0 | prerelax(); |
178 | 0 | relaxer = new VisRunner(); |
179 | 0 | relaxer.start(); |
180 | 0 | } |
181 | ||
182 | /** | |
183 | * Restarts layout, then calls init(); | |
184 | */ | |
185 | public synchronized void restart() { | |
186 | 0 | if (visRunnerIsRunning ) { |
187 | 0 | stop(); |
188 | //throw new FatalException("Can't restart while a visrunner is running"); | |
189 | } | |
190 | 0 | stop = false; |
191 | 0 | layout.restart(); |
192 | 0 | init(); |
193 | 0 | fireStateChanged(); |
194 | 0 | } |
195 | ||
196 | /** | |
197 | * Runs the visualization forward a few hundred iterations (for half a | |
198 | * second) | |
199 | */ | |
200 | public void prerelax() { | |
201 | 0 | suspend(); |
202 | ||
203 | 0 | int i = 0; |
204 | 0 | if (layout.isIncremental()) { |
205 | // then increment layout for half a second | |
206 | 0 | long timeNow = System.currentTimeMillis(); |
207 | 0 | while (System.currentTimeMillis() - timeNow < 500 && !layout.incrementsAreDone()) { |
208 | 0 | i++; |
209 | 0 | layout.advancePositions(); |
210 | } | |
211 | } | |
212 | 0 | unsuspend(); |
213 | 0 | } |
214 | ||
215 | /** | |
216 | * If the visualization runner is not yet running, kick it off. | |
217 | */ | |
218 | public synchronized void start() { | |
219 | 0 | synchronized (pauseObject) { |
220 | 0 | pauseObject.notifyAll(); |
221 | 0 | } |
222 | 0 | } |
223 | ||
224 | /** | |
225 | * set a flag to suspend the relaxer thread | |
226 | */ | |
227 | public synchronized void suspend() { | |
228 | 0 | manualSuspend = true; |
229 | 0 | } |
230 | ||
231 | /** | |
232 | * un-set the suspend flag for the relaxer thead | |
233 | */ | |
234 | public synchronized void unsuspend() { | |
235 | 0 | manualSuspend = false; |
236 | 0 | synchronized (pauseObject) { |
237 | 0 | pauseObject.notifyAll(); |
238 | 0 | } |
239 | 0 | } |
240 | ||
241 | 0 | public Object pauseObject = new String("PAUSE OBJECT"); |
242 | ||
243 | 0 | long[] relaxTimes = new long[5]; |
244 | 0 | long[] paintTimes = new long[5]; |
245 | 0 | int relaxIndex = 0; |
246 | 0 | int paintIndex = 0; |
247 | double paintfps, relaxfps; | |
248 | ||
249 | ||
250 | 0 | boolean stop = false; |
251 | ||
252 | 0 | boolean visRunnerIsRunning = false; |
253 | ||
254 | /** | |
255 | * the relaxer thread that applies the Layout algorithm to the graph | |
256 | * | |
257 | */ | |
258 | protected class VisRunner extends Thread { | |
259 | public VisRunner() { | |
260 | super("Relaxer Thread"); | |
261 | } | |
262 | ||
263 | public void run() { | |
264 | visRunnerIsRunning = true; | |
265 | try { | |
266 | while (!layout.incrementsAreDone() && !stop) { | |
267 | synchronized (pauseObject) { | |
268 | while (manualSuspend && !stop) { | |
269 | try { | |
270 | pauseObject.wait(); | |
271 | } catch (InterruptedException e) { | |
272 | // System.err.println("vis runner wait interrupted"); | |
273 | } | |
274 | } | |
275 | } | |
276 | long start = System.currentTimeMillis(); | |
277 | layout.advancePositions(); | |
278 | long delta = System.currentTimeMillis() - start; | |
279 | ||
280 | if (stop) | |
281 | return; | |
282 | ||
283 | String status = layout.getStatus(); | |
284 | if (statusCallback != null && status != null) { | |
285 | statusCallback.callBack(status); | |
286 | } | |
287 | ||
288 | if (stop) | |
289 | return; | |
290 | ||
291 | relaxTimes[relaxIndex++] = delta; | |
292 | relaxIndex = relaxIndex % relaxTimes.length; | |
293 | relaxfps = average(relaxTimes); | |
294 | ||
295 | if (stop) | |
296 | return; | |
297 | fireStateChanged(); | |
298 | ||
299 | if (stop) | |
300 | return; | |
301 | ||
302 | try { | |
303 | sleep(relaxerThreadSleepTime); | |
304 | } catch (InterruptedException ie) { | |
305 | // System.err.println("vis runner sleep insterrupted"); | |
306 | } | |
307 | } | |
308 | } finally { | |
309 | visRunnerIsRunning = false; | |
310 | } | |
311 | } | |
312 | } | |
313 | ||
314 | /** | |
315 | * Returns a flag that says whether the visRunner thread is running. If | |
316 | * it is not, then you may need to restart the thread. | |
317 | */ | |
318 | public boolean isVisRunnerRunning() { | |
319 | 0 | return visRunnerIsRunning; |
320 | } | |
321 | ||
322 | /** | |
323 | * Returns the double average of a number of long values. | |
324 | * @param paintTimes an array of longs | |
325 | * @return the average of the doubles | |
326 | */ | |
327 | protected double average(long[] paintTimes) { | |
328 | 0 | double l = 0; |
329 | 0 | for (int i = 0; i < paintTimes.length; i++) { |
330 | 0 | l += paintTimes[i]; |
331 | } | |
332 | 0 | return l / paintTimes.length; |
333 | } | |
334 | ||
335 | /** | |
336 | * @param scb | |
337 | */ | |
338 | public void setTextCallback(StatusCallback scb) { | |
339 | 0 | this.statusCallback = scb; |
340 | 0 | } |
341 | ||
342 | /** | |
343 | * set a flag to stop the VisRunner relaxer thread | |
344 | */ | |
345 | public synchronized void stop() { | |
346 | 0 | manualSuspend = false; |
347 | 0 | stop = true; |
348 | // interrupt the relaxer, in case it is paused or sleeping | |
349 | // this should ensure that visRunnerIsRunning gets set to false | |
350 | 0 | try { relaxer.interrupt(); } |
351 | 0 | catch(Exception ex) { |
352 | // the applet security manager may have prevented this. | |
353 | // just sleep for a second to let the thread stop on its own | |
354 | 0 | System.err.println("got "+ex); |
355 | 0 | try { Thread.sleep(1000); } |
356 | 0 | catch(InterruptedException ie) {} // swallow |
357 | 0 | } |
358 | 0 | synchronized (pauseObject) { |
359 | 0 | pauseObject.notifyAll(); |
360 | 0 | } |
361 | 0 | } |
362 | ||
363 | /** | |
364 | * Adds a <code>ChangeListener</code>. | |
365 | * @param l the listener to be added | |
366 | */ | |
367 | public void addChangeListener(ChangeListener l) { | |
368 | 0 | changeSupport.addChangeListener(l); |
369 | 0 | } |
370 | ||
371 | /** | |
372 | * Removes a ChangeListener. | |
373 | * @param l the listener to be removed | |
374 | */ | |
375 | public void removeChangeListener(ChangeListener l) { | |
376 | 0 | changeSupport.removeChangeListener(l); |
377 | 0 | } |
378 | ||
379 | /** | |
380 | * Returns an array of all the <code>ChangeListener</code>s added | |
381 | * with addChangeListener(). | |
382 | * | |
383 | * @return all of the <code>ChangeListener</code>s added or an empty | |
384 | * array if no listeners have been added | |
385 | */ | |
386 | public ChangeListener[] getChangeListeners() { | |
387 | 0 | return changeSupport.getChangeListeners(); |
388 | } | |
389 | ||
390 | /** | |
391 | * Notifies all listeners that have registered interest for | |
392 | * notification on this event type. The event instance | |
393 | * is lazily created. | |
394 | * The primary listeners will be views that need to be repainted | |
395 | * because of changes in this model instance | |
396 | * @see EventListenerList | |
397 | */ | |
398 | public void fireStateChanged() { | |
399 | 0 | changeSupport.fireStateChanged(); |
400 | 0 | } |
401 | ||
402 | } |
this report was generated by version 1.0.5 of jcoverage. |
copyright © 2003, jcoverage ltd. all rights reserved. |