| 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 AbstractLayout) { |
| 117 | 0 | ((AbstractLayout)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. |