/*
* #!
* Ontopia Vizigator
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* !#
*/
package net.ontopia.topicmaps.viz;
import java.awt.event.ActionListener;
import java.util.Timer;
import java.util.TimerTask;
import com.touchgraph.graphlayout.TGPanel;
/**
* Stops the motion of the nodes in Vizigator at certain intervals.
* Has methods for that lets the MotionKiller waits for a given amount of time
* before stopping the motion, so that the nodes can be arranged reasonably
* first.
* Also has a counter that helps operations outside the OKS, which cannot call
* the waitFor() operation, so that these operations are given a bit more time
* (on average) to finish.
* WEAKNESSES:
* There are some weaknesses to this approach.
* First of all, MotionKiller may run immediately after operations that are
* outside the OKS, since these cannot ask MotionKiller to wait.
* - This would be easily solvable if we modified the TouchGraph code.
* Another disadvantage is that MotionKiller compromised with the algorithm in
* TouchGraph which makes sure nodes and edges are arranged optimally to avoid
* clutter. For small graphs this is normally not a problem, but large graphs
* which take time to arrange optimally may end up much more cluttered than
* necessary. Possible solutions to this problem are:
* - Let the user control the parameters of this class (easy to implement)
* - Improve the layout algorithm of TouchGraph (probably hard)
* - Hope that TouchGraph has/will implement an improved layout algorithm.
*/
public class MotionKiller extends TimerTask {
// Note: VizPanel uses this variable set the initial state of a menu.
public static final boolean INITIALLY_ENABLED = false;
protected TGPanel tgPanel;
protected long waitUntil1;
protected long waitUntil2;
protected int cycle = 0;
protected int maxCycle = 3;
protected boolean enabled;
long millis;
Timer timer;
/**
* Create a MotionKiller for the given tgPanel, scheduled to run every
* 'millis' number of milliseconds after creation.
* @param tgPanel The TGPanel for which to stop the motion.
* @param millis The milliseconds between every time the motion is stopped
*/
public MotionKiller(TGPanel tgPanel, long millis) {
this.tgPanel = tgPanel;
waitUntil1 = 0;
waitUntil2 = 0;
this.millis = millis;
timer = new Timer();
timer.scheduleAtFixedRate(this, millis, millis);
// Note: VizPanel assumes this assignment when building menus.
enabled = INITIALLY_ENABLED;
setEnabled(enabled);
}
/**
* Wait for 'duration1' milliseconds before slowing down the motion,
* then wait for 'duration2' milliseconds before stopping the motion,
*/
public void waitFor(long duration1, long duration2) {
long currentTime = System.currentTimeMillis();
waitUntil1 = currentTime + duration1;
waitUntil2 = currentTime + duration2;
}
/**
* This method is called on schedula by the timer.
*/
public void run() {
if (!enabled)
return;
stopMotion();
}
/**
* Enables/disables this motion killer.
* Note: VizPanel uses the value of enabled to build menus, so this method
* should only be changed (indirectly) from there.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
tgPanel.resetDamper();
// The following code causes exception bacause one cannot cancel a task
// and then reschedule it.
// So currently, the task keeps running forever, even if MotionKiller is
// disabled. Would be nice to turn it off completely, since it interrupts
// once a second.
// if (enabled) {
// timer = new Timer();
// timer.scheduleAtFixedRate(this, millis, millis);
// tgPanel.resetDamper();
// } else if (timer != null) {
// timer.cancel();
// timer = null;
// }
}
public boolean getEnabled() {
return enabled;
}
private void stopMotion() {
if (waitUntil1 == 0 && waitUntil2 == 0) {
// This execution is not soon after call to a waitFor(), but it
// may be after some other procedure that requires
// layouting. Wait a bit longer than usual by only executing
// some of the times (frequency: 1 / maxCycle)
cycle++;
if (cycle >= maxCycle)
cycle = 0;
else
return;
}
if (waitUntil1 != 0) {
// This execution is soon after a call to a waitFor().
// Check if the wait to slow down the motion has been long enough.
if (System.currentTimeMillis() < waitUntil1)
return;
waitUntil1 = 0;
}
tgPanel.stopMotion();
if (waitUntil2 != 0) {
// This execution is soon after a call to a waitFor().
// Check if the wait to stop the motion has been long enough.
if (System.currentTimeMillis() < waitUntil2)
return;
waitUntil2 = 0;
}
tgPanel.stopMotion();
}
protected void setMaxCycle(int maxCycle) {
this.maxCycle = maxCycle;
VizDebugUtils.debug("MotionKiller - maxCycle: " + maxCycle);
}
}