diff --git a/mono/Assistance/DynamicSurface.cs b/mono/Assistance/DynamicSurface.cs
new file mode 100644
index 0000000..9655b9d
--- /dev/null
+++ b/mono/Assistance/DynamicSurface.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+
+namespace Assistance {
+	public class DynamicSurface: IDisposable {
+		private readonly double incrementScale;
+	
+		private int offsetX;
+		private int offsetY;
+		private Cairo.ImageSurface surface;
+		
+		public DynamicSurface(double incrementScale = 1.2)
+			{ this.incrementScale = incrementScale; }
+
+		public bool isEmpty
+			{ get { return surface == null; } }
+			
+		public int left
+			{ get { return isEmpty ? 0 : offsetX; } }
+		public int top
+			{ get { return isEmpty ? 0 : offsetY; } }
+		public int width
+			{ get { return isEmpty ? 0 : surface.Width; } }
+		public int height
+			{ get { return isEmpty ? 0 : surface.Height; } }
+		
+		public Rectangle getBounds() {
+			return isEmpty ? new Rectangle()
+			     : new Rectangle((double)offsetX, (double)offsetY, (double)(offsetX + surface.Width), (double)(offsetY + surface.Height));
+		}
+
+		public Cairo.Context getContext() {
+			if (isEmpty) return null;
+			Cairo.Context context = new Cairo.Context(surface);
+			context.Antialias = Cairo.Antialias.Gray;
+			context.Translate(-offsetX, -offsetY);
+			return context;
+		}
+
+		public void draw(Cairo.Context context, double alpha = 1.0) {
+			if (isEmpty) return;
+			context.Save();
+			context.Translate(offsetX, offsetY);
+			context.SetSource(surface);
+			if (alpha >= 1.0 - Geometry.precision)
+				context.Paint(); else context.PaintWithAlpha(alpha);
+			context.Restore();
+		}
+
+		public void clear() {
+			if (isEmpty) return;
+			surface.Dispose();
+			surface = null;
+		}
+
+		private bool doExpand(Rectangle rect, bool noScale) {
+			int rl = (int)Math.Floor(rect.x0 + Geometry.precision);
+			int rt = (int)Math.Floor(rect.y0 + Geometry.precision);
+			int rr = Math.Max(rl, (int)Math.Ceiling(rect.x1 - Geometry.precision));
+			int rb = Math.Max(rt, (int)Math.Ceiling(rect.y1 - Geometry.precision));
+		    
+		    int l, t, r, b;
+		    if (surface == null) {
+				l = rl; t = rt; r = rr; b = rb;
+		    } else {
+				l = offsetX;
+				t = offsetY;
+				r = l + surface.Width;
+				b = t + surface.Height;
+			}
+			
+		    int incX = noScale ? 0 : Math.Max(0, (int)Math.Ceiling( (incrementScale - 1.0)*(Math.Max(r, rr) - Math.Min(l, rl)) ));
+		    int incY = noScale ? 0 : Math.Max(0, (int)Math.Ceiling( (incrementScale - 1.0)*(Math.Max(b, rb) - Math.Min(t, rt)) ));
+
+		    if (rl < l) l = rl - incX;
+		    if (rt < t) t = rt - incY;
+		    if (rr > r) r = rr + incX;
+		    if (rb > b) b = rb + incY;
+		    
+		    int w = r - l;
+		    int h = b - t;
+		    if (surface != null && l == offsetX && t == offsetY && w == surface.Width && h == surface.Height)
+		    	return false;
+
+			Cairo.ImageSurface newSurface = new Cairo.ImageSurface(Cairo.Format.ARGB32, w, h);
+	    	Cairo.Context context = new Cairo.Context(newSurface);
+	    	if (surface != null) {
+		    	context.Translate(offsetX - l, offsetY - t);
+		    	context.SetSource(surface);
+		    	context.Paint();
+				context.GetTarget().Flush();
+				context.Dispose();
+			}
+			
+			offsetX = l;
+			offsetY = t;
+			surface = newSurface;
+			
+			return true;
+		}
+
+		public bool expand(Rectangle rect, bool noScale = false) {
+			Cairo.Surface surface = this.surface;
+			if (doExpand(rect, noScale)) {
+				if (surface != null) surface.Dispose();
+				return true;
+			}
+			return false;
+		}
+		
+		public bool expandContext(ref Cairo.Context context, Rectangle rect, bool noScale = false) {
+			Cairo.Surface surface = this.surface;
+			if (context != null) context.GetTarget().Flush();
+			if (doExpand(rect, noScale)) {
+				Cairo.Context oldContext = context;
+				context = getContext();
+				if (oldContext != null) oldContext.Dispose();
+				surface.Dispose();
+				return true;
+			}
+			return false;
+		}
+
+		public void Dispose()
+			{ Dispose(true); GC.SuppressFinalize(this); }
+		protected virtual void Dispose(bool disposing)
+			{  clear(); }
+		~DynamicSurface()
+			{ Dispose(false); }
+	}
+}
+
diff --git a/mono/Assistance/InputManager.cs b/mono/Assistance/InputManager.cs
index 2023aff..dff039c 100644
--- a/mono/Assistance/InputManager.cs
+++ b/mono/Assistance/InputManager.cs
@@ -55,6 +55,7 @@ namespace Assistance {
 		public interface IModifier: Track.IOwner {
 			void activate();
 			void modify(List<Track> tracks, KeyPoint keyPoint, List<Track> outTracks);
+			void draw(Cairo.Context context, List<Track> tracks);
 			void deactivate();
 		}
 
@@ -63,6 +64,9 @@ namespace Assistance {
 			public virtual void modify(Track track, KeyPoint keyPoint, List<Track> outTracks) { }
 			public virtual void modify(List<Track> tracks, KeyPoint keyPoint, List<Track> outTracks)
 				{ foreach(Track track in tracks) modify(track, keyPoint, outTracks); }
+			public virtual void draw(Cairo.Context context, Track tracks) { }
+			public virtual void draw(Cairo.Context context, List<Track> tracks)
+				{ foreach(Track track in tracks) draw(context, track); }
 			public virtual void deactivate() { }
 		}
 
@@ -70,22 +74,20 @@ namespace Assistance {
 		public readonly Workarea workarea;
 		
 		private readonly InputState state = new InputState();
-
+		
+		private bool wantActive;
+		private bool actualActive;
 		private Tool tool;
 		private readonly List<IModifier> modifiers = new List<IModifier>();
 
-		private readonly List<Track> tracks = new List<Track>();
+		private readonly List<List<Track>> tracks = new List<List<Track>>() { new List<Track>() };
 		private readonly List<KeyPoint> keyPoints = new List<KeyPoint>();
 		private int keyPointsSent;
 
-		private List<Track> subTracks = null;
-		private readonly List<Track>[] subTracksBuf = new List<Track>[] { new List<Track>(), new List<Track>() };
 
-
-		InputManager(Workarea workarea)
+		public InputManager(Workarea workarea)
 			{ this.workarea = workarea; }
 
-
 		private void paintRollbackTo(int keyIndex, List<Track> subTracks) {
 			if (keyIndex >= keyPoints.Count)
 				return;
@@ -146,21 +148,18 @@ namespace Assistance {
 		
 		private void paintTracks() {
 			bool allFinished = true;
-			foreach(Track track in tracks)
+			foreach(Track track in tracks[0])
 				if (!track.isFinished())
 					{ allFinished = false; break; }
 
 			while(true) {
 				// run modifiers
 				KeyPoint newKeyPoint = new KeyPoint();
-				subTracks = tracks;
-				int i = 0;
-				foreach(IModifier modifier in modifiers) {
-					List<Track> outTracks = subTracksBuf[i];
-					modifier.modify(subTracks, newKeyPoint, outTracks);
-					subTracks = outTracks;
-					i = 1 - i;
+				for(int i = 0; i < modifiers.Count; ++i) {
+					tracks[i+1].Clear();
+					modifiers[i].modify(tracks[i], newKeyPoint, tracks[i+1]);
 				}
+				List<Track> subTracks = tracks[modifiers.Count];
 				
 				// create handlers	
 				foreach(Track track in subTracks)
@@ -195,8 +194,8 @@ namespace Assistance {
 				if (newKeyPoint.isFree) {
 					if (allFinished) {
 						paintApply(keyPoints.Count, subTracks);
-						tracks.Clear();
-						this.subTracks.Clear();
+						foreach(List<Track> ts in tracks)
+							ts.Clear();
 					}
 					break;
 				}
@@ -223,7 +222,7 @@ namespace Assistance {
 				touchId,
 				state.keyHistoryHolder(ticks),
 				state.buttonHistoryHolder(device, ticks) );
-			tracks.Insert(index, track);
+			tracks[0].Insert(index, track);
 			return track;
 		}
 		
@@ -231,23 +230,23 @@ namespace Assistance {
 			int cmp;
 			
 			int a = 0;
-			cmp = trackCompare(tracks[a], device, touchId);
-			if (cmp == 0) return tracks[a];
+			cmp = trackCompare(tracks[0][a], device, touchId);
+			if (cmp == 0) return tracks[0][a];
 			if (cmp < 0) return createTrack(a, device, touchId, ticks);
 			
 			int b = tracks.Count - 1;
-			cmp = trackCompare(tracks[b], device, touchId);
-			if (cmp == 0) return tracks[b];
+			cmp = trackCompare(tracks[0][b], device, touchId);
+			if (cmp == 0) return tracks[0][b];
 			if (cmp > 0) return createTrack(b+1, device, touchId, ticks);
 			
 			// binary search: tracks[a] < tracks[c] < tracks[b]
 			while(true) {
 				int c = (a + b)/2;
 				if (a == c) break;
-				cmp = trackCompare(tracks[c], device, touchId);
+				cmp = trackCompare(tracks[0][c], device, touchId);
 				if (cmp < 0) b = c; else
 					if (cmp > 0) a = c; else
-						return tracks[c];
+						return tracks[0][c];
 			}
 			return createTrack(b, device, touchId, ticks);
 		}
@@ -275,18 +274,46 @@ namespace Assistance {
 		}
 		
 		private void touchTracks(bool finish = false) {
-			foreach(Track track in tracks)
+			foreach(Track track in tracks[0])
 				if (!track.isFinished() && track.points.Count > 0)
 					addTrackPoint(track, track.getLast().point, track.getLast().time, finish);
 			paintTracks();
 		}
 		
-		private void finishTracks()
-			{ touchTracks(true); }
+		private void actualActivate() {
+			bool wasActive = actualActive;
+			actualActive = wantActive && tool != null;
+			if (wasActive == actualActive) return;
+			
+			if (actualActive) {
+				foreach(IModifier modifier in modifiers)
+					modifier.activate();
+				tool.activate();
+			} else {
+				touchTracks(true);
+				tool.deactivate();
+				foreach(IModifier modifier in modifiers)
+					modifier.deactivate();
+			}
+		}
+		
+		public bool isActive()
+			{ return actualActive; }
+		public void activate()
+			{ wantActive = true; actualActivate(); }
+		public void deactivate()
+			{ wantActive = false; actualActivate(); }
+		public void finishTracks()
+			{ if (isActive()) touchTracks(true); }
+			
+		public List<Track> getInputTracks()
+			{ return tracks[0]; }
+		public List<Track> getOutputTracks()
+			{ return tracks[modifiers.Count]; }
 		
 		
-		public void trackEvent(long touchId, Gdk.Device device, Track.Point point, bool final, long ticks) {
-			if (tool != null) {
+		public void trackEvent(Gdk.Device device, long touchId, Track.Point point, bool final, long ticks) {
+			if (isActive()) {
 				Track track = getTrack(device, touchId, ticks);
 				if (!track.isFinished()) {
 					double time = (double)(ticks - track.keyHistory.ticks)*Timer.step - track.keyHistory.timeOffset;
@@ -298,7 +325,7 @@ namespace Assistance {
 
 		public void keyEvent(bool press, Gdk.Key key, long ticks) {
 			state.keyEvent(press, key, ticks);
-			if (tool != null) {
+			if (isActive()) {
 				tool.keyEvent(press, key, state);
 				touchTracks();
 			}
@@ -306,27 +333,30 @@ namespace Assistance {
 		
 		public void buttonEvent(bool press, Gdk.Device device, uint button, long ticks) {
 			state.buttonEvent(press, device, button, ticks);
-			if (tool != null) {
+			if (isActive()) {
 				tool.buttonEvent(press, device, button, state);
 				touchTracks();
 			}
 		}
 	
-		
 		public Tool getTool()
 			{ return tool; }
 		
 		public void setTool(Tool tool) {
 			if (this.tool == tool) {
-				if (this.tool != null) {
+				if (actualActive) {
 					finishTracks();
 					this.tool.deactivate();
 				}
 				
 				this.tool = tool;
 				
-				if (this.tool != null)
-					this.tool.activate();
+				if (actualActive) {
+					if (this.tool != null)
+						this.tool.activate();
+					else
+						actualActivate();
+				}
 			}
 		}
 		
@@ -336,17 +366,23 @@ namespace Assistance {
 			{ return modifiers[index]; }
 
 		public void insertModifier(int index, IModifier modifier) {
-			if (this.tool != null) finishTracks();
+			if (actualActive)
+				finishTracks();
 			modifiers.Insert(index, modifier);
-			modifier.activate();
+			tracks.Insert(index+1, new List<Track>());
+			if (actualActive)
+				modifier.activate();
 		}
 		public void addModifier(IModifier modifier)
 			{ insertModifier(getModifiersCount(), modifier); }
 		
 		public void removeModifier(int index) {
-			if (this.tool != null) finishTracks();
-			modifiers[index].deactivate();
+			if (actualActive) {
+				finishTracks();
+				modifiers[index].deactivate();
+			}
 			modifiers.RemoveAt(index);
+			tracks.RemoveAt(index+1);
 		}
 		public void removeModifier(IModifier modifier) {
 			for(int i = 0; i < getModifiersCount(); ++i)
@@ -355,16 +391,16 @@ namespace Assistance {
 		}
 		public void clearModifiers() {
 			while(getModifiersCount() > 0)
-				removeModifier(0);
+				removeModifier(getModifiersCount() - 1);
 		}
 	
 		
 		public void draw(Cairo.Context context) {
 			// paint not sent sub-tracks
-			if (subTracks != null && keyPointsSent < keyPoints.Count) {
+			if (keyPointsSent < keyPoints.Count) {
 				context.Save();
 				penPreview.apply(context);
-				foreach(Track track in subTracks) {
+				foreach(Track track in getOutputTracks()) {
 					TrackHandler handler = (TrackHandler)track.handler;
 					int start = handler.keys[keyPointsSent];
 					if (start < track.points.Count) {
@@ -388,6 +424,10 @@ namespace Assistance {
 				context.Stroke();
 				context.Restore();
 			}
+			
+			// paint modifiers
+			for(int i = 0; i < modifiers.Count; ++i)
+				modifiers[i].draw(context, tracks[i]);
 		}
 	}
 }
diff --git a/mono/Assistance/InputModifierAssistants.cs b/mono/Assistance/InputModifierAssistants.cs
index 5a9adf1..43a7841 100644
--- a/mono/Assistance/InputModifierAssistants.cs
+++ b/mono/Assistance/InputModifierAssistants.cs
@@ -5,11 +5,11 @@ using System.Collections.Generic;
 namespace Assistance {
 	public class InputModifierAssistants: InputModifierTangents {
 		public readonly Workarea workarea;
-		public readonly bool interpolate;
+		public readonly bool defaultTangents;
 		
-		public InputModifierAssistants(Workarea workarea, bool interpolate = false) {
+		public InputModifierAssistants(Workarea workarea, bool defaultTangents = false) {
 			this.workarea = workarea;
-			this.interpolate = interpolate;
+			this.defaultTangents = defaultTangents;
 		}
 		
 		public new class Modifier: Track.Modifier {
@@ -18,6 +18,7 @@ namespace Assistance {
 			
 			public InputManager.KeyPoint.Holder holder = null;
 			public List<Guideline> guidelines = new List<Guideline>();
+			public List<Guideline> hintGuidelines = new List<Guideline>();
 			
 			public override Track.WayPoint calcWayPoint(double originalIndex) {
 				Track.WayPoint p = original.calcWayPoint(originalIndex);
@@ -36,7 +37,7 @@ namespace Assistance {
 				Track.Handler handler = new Track.Handler(this, track);
 				modifier = new Modifier(track.handler);
 				workarea.getGuidelines(modifier.guidelines, track.points[0].point.position);
-				if (interpolate && modifier.guidelines.Count == 0)
+				if (defaultTangents && modifier.guidelines.Count == 0)
 					{ base.modify(track, keyPoint, outTracks); return; }
 				
 				track.handler = handler;
@@ -51,7 +52,7 @@ namespace Assistance {
 			}
 			
 			subTrack = track.handler.tracks[0];
-			if (subTrack.modifier is InputModifierTangents.Modifier)
+			if (!(subTrack.modifier is Modifier))
 				{ base.modify(track, keyPoint, outTracks); return; }
 			
 			modifier = (Modifier)subTrack.modifier;
@@ -91,6 +92,20 @@ namespace Assistance {
 				subTrack.points.Add(modifier.calcWayPoint(i));
 			subTrack.wayPointsAdded = subTrack.points.Count - start;
 		}
+		
+		public override void draw(Cairo.Context context, Track track) {
+			if (track.handler == null) return;
+			Track subTrack = track.handler.tracks[0];
+			if (!(subTrack.modifier is Modifier))
+				{ base.draw(context, track); return; }
+			Modifier modifier = (Modifier)subTrack.modifier;
+			if (modifier.guidelines.Count > 0 && subTrack.points.Count > 0) {
+				workarea.getGuidelines(modifier.hintGuidelines, subTrack.points[0].point.position);
+				foreach(Guideline gl in modifier.hintGuidelines)
+					gl.draw(context);
+				modifier.hintGuidelines.Clear();
+			}
+		}
 	}
 }
 
diff --git a/mono/Assistance/InputModifierInterpolation.cs b/mono/Assistance/InputModifierInterpolation.cs
index 51c63d7..bcd4b60 100644
--- a/mono/Assistance/InputModifierInterpolation.cs
+++ b/mono/Assistance/InputModifierInterpolation.cs
@@ -26,7 +26,6 @@ namespace Assistance {
 			}
 			
 			Track subTrack = track.handler.tracks[0];
-			Track.Modifier modifier = subTrack.modifier;
 			outTracks.Add(subTrack);
 			
 			if (!track.isChanged)
diff --git a/mono/Assistance/MainWindow.cs b/mono/Assistance/MainWindow.cs
index 8f4a0cd..8a3d7ea 100644
--- a/mono/Assistance/MainWindow.cs
+++ b/mono/Assistance/MainWindow.cs
@@ -14,19 +14,19 @@ namespace Assistance {
 		Cairo.ImageSurface surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 1, 1);
 
 		Workarea workarea = new Workarea();
+		ToolFull toolFull;
+		
 		bool dragging = false;
+		bool painting = false;
 		ActivePoint activePoint;
 
-		uint timeStart = 0;
-		long ticksStart = 0;
-		long lastTicks = 0;
 		Point offset;
 		Point cursor;
 		
-		InputState inputState = new InputState();
+		long touchId;
+		long ticksStart = 0;
+		double timeStart = 0;
 		
-		Track track = null;
-
 		public MainWindow(): base(Gtk.WindowType.Toplevel) {
 			foreach(Gdk.Device device in Display.ListDevices()) {
 				if (device.Name.Contains("tylus")) {
@@ -42,6 +42,9 @@ namespace Assistance {
 			       | Gdk.EventMask.ButtonMotionMask
 			       | Gdk.EventMask.PointerMotionMask;
 			ExtensionEvents = Gdk.ExtensionMode.All;
+			
+			toolFull = new ToolFull(workarea);
+			workarea.setTool(toolFull);
         }
         
         protected override bool OnDeleteEvent(Gdk.Event e) {
@@ -95,19 +98,20 @@ namespace Assistance {
 			activePoint.bringToFront();
 		}
 
-		private void beginTrack(Gdk.Device device) {
+		private void beginTrack(double timeStart) {
 			endDragAndTrack();
-			track = new Track(Track.getTouchId(), device);
+			++touchId;
 			ticksStart = Timer.ticks();
+			this.timeStart = timeStart;
 		}
 
 		private void endDragAndTrack() {
 			dragging = false;
 			offset = new Point();
-			
-			if (track != null)
-				workarea.paintTrack(track);
-			track = null;
+			if (painting) {
+				painting = false;
+				workarea.inputManager.finishTracks();
+			}
 		}
 		
 		protected override bool OnKeyPressEvent(Gdk.EventKey e) {
@@ -116,10 +120,6 @@ namespace Assistance {
 				new AssistantVanishingPoint(workarea.document, cursor);
 				endDragAndTrack();
 				break;
-			case Gdk.Key.Key_2:
-				new AssistantGrid(workarea.document, cursor);
-				endDragAndTrack();
-				break;
 			case Gdk.Key.Q:
 			case Gdk.Key.q:
 				new ModifierSnowflake(workarea.document, cursor);
@@ -137,8 +137,7 @@ namespace Assistance {
 				endDragAndTrack();
 				break;
 			default:
-				inputState.keyPress(e.Key, Timer.ticks());
-				retryTrackPoint();
+				workarea.inputManager.keyEvent(true, e.Key, Timer.ticks());
 				break;
 			}
 			QueueDraw();
@@ -146,46 +145,29 @@ namespace Assistance {
 		}
 		
 		protected override bool OnKeyReleaseEvent(Gdk.EventKey e) {
-			inputState.keyRelease(e.Key, Timer.ticks());
-			retryTrackPoint();
+			workarea.inputManager.keyEvent(false, e.Key, Timer.ticks());
 			return base.OnKeyReleaseEvent(e);
 		}
 		
-		void addTrackPoint(Point p, double time, double pressure, Point tilt) {
-			if (track == null)
+		void addTrackPoint(Gdk.Device device, Point p, double time, double pressure, Point tilt, bool final) {
+			if (!painting)
 				return;
 
-			TrackPoint point = new TrackPoint();
-			point.point = p;
-			point.time = time;
+			Track.Point point = new Track.Point();
+			point.position = p;
 			point.pressure = pressure;
-			point.tilt = tilt;
+			point.tilt = tilt;	
 			
-			long ticks = Timer.ticks();
-			if (track.points.Count > 0) {
-				double t = track.points[ track.points.Count-1 ].time;
-				if (point.time - t < Geometry.precision)
-					point.time = t + (ticks - lastTicks)*Timer.step;
-			}
-			lastTicks = ticks;
-
-			point.keyState.state = inputState.keyState;
-			point.keyState.ticks = ticksStart;
-			point.keyState.timeOffset = point.time;
+			long ticks = ticksStart + (long)Math.Round((time - timeStart)*Timer.frequency);
 			
-			point.buttonState.state = inputState.buttonState(track.device);
-			point.buttonState.ticks = ticksStart;
-			point.buttonState.timeOffset = point.time;
-			
-			track.points.Add(point);
+			workarea.inputManager.trackEvent(device, touchId, point, final, ticks);
 		}
 
-		void addTrackPoint(double x, double y, uint t, Gdk.Device device, double[] axes) {
+		void addTrackPoint(double x, double y, uint t, Gdk.Device device, double[] axes, bool final) {
 			Point point = windowToWorkarea(new Point(x, y));
 			double time = (double)(t - timeStart)*0.001;
 			double pressure = 0.5;
 			Point tilt = new Point(0.0, 0.0);
-			
 			if (device != null && axes != null) {
 				double v;
 				if (device.GetAxis(axes, Gdk.AxisUse.Pressure, out v))
@@ -195,46 +177,25 @@ namespace Assistance {
 				if (device.GetAxis(axes, Gdk.AxisUse.Ytilt, out v))
 					tilt.y = v;
 			}
-			
-			long ticks = Timer.ticks();
-			long dticks = Math.Max(1, ticks - lastTicks);
-			lastTicks = ticks;
-			if (track.points.Count > 0) {
-				double prevTime = track.points[ track.points.Count-1 ].time;
-				if (time - prevTime < Geometry.precision)
-					time = prevTime + dticks*Timer.step;
-			}
-
-			addTrackPoint(point, time, pressure, tilt);
+			addTrackPoint(device, point, time, pressure, tilt, final);
 		}
 		
-		void addTrackPoint(Gdk.EventButton e) {
-			addTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes);
-		}
-
-		void addTrackPoint(Gdk.EventMotion e) {
-			addTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes);
-		}
-
-		void retryTrackPoint() {
-			if (track == null || track.points.Count < 1) return;
-			TrackPoint last = track.points[track.points.Count-1];
-			addTrackPoint(last.point, last.time, last.pressure, last.tilt);
-		}
+		void addTrackPoint(Gdk.EventButton e, bool press)
+			{ addTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes, !press); }
+		void addTrackPoint(Gdk.EventMotion e)
+			{ addTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes, false); }
 
 		protected override bool OnButtonPressEvent(Gdk.EventButton e) {
 			cursor = windowToWorkarea(new Point(e.X, e.Y));
-			Console.WriteLine(e.Button.ToString());
-			inputState.buttonPress(e.Device, e.Button, Timer.ticks());
-			retryTrackPoint();
+			workarea.inputManager.buttonEvent(true, e.Device, e.Button, Timer.ticks());
 			if (e.Button == 1) {
 				timeStart = e.Time;
 				activePoint = workarea.findPoint(cursor);
 				if (activePoint != null) {
 					beginDrag();
 				} else {
-					beginTrack(e.Device);
-					addTrackPoint(e);
+					beginTrack((double)e.Time*0.001);
+					addTrackPoint(e, true);
 				}
 			}
 			QueueDraw();
@@ -243,13 +204,12 @@ namespace Assistance {
 
 		protected override bool OnButtonReleaseEvent(Gdk.EventButton e) {
 			cursor = windowToWorkarea(new Point(e.X, e.Y));
-			inputState.buttonRelease(e.Device, e.Button, Timer.ticks());
-			retryTrackPoint();
+			workarea.inputManager.buttonEvent(false, e.Device, e.Button, Timer.ticks());
 			if (e.Button == 1) {
-				if (!dragging && track != null)
-					addTrackPoint(e);
+				if (!dragging && painting)
+					addTrackPoint(e, false);
 				endDragAndTrack();
-				if (!dragging && track == null)
+				if (!dragging && !painting)
 					activePoint = workarea.findPoint(cursor);
 			}
 			QueueDraw();
@@ -261,8 +221,7 @@ namespace Assistance {
 			if (dragging) {
 				activePoint.owner.onMovePoint(activePoint, cursor + offset);
 			} else
-			if (track != null) {
-				if (e.IsHint) Gdk.Display.Default.Beep();
+			if (painting) {
 				addTrackPoint(e);
 			} else {
 				activePoint = workarea.findPoint(cursor);
@@ -273,7 +232,7 @@ namespace Assistance {
 
         public void draw(Cairo.Context context) {
         	context.Translate(surface.Width/2, surface.Height/2);
-			workarea.draw(context, activePoint, cursor + offset, track);
+			workarea.draw(context, activePoint);
         }
     }
 }
diff --git a/mono/Assistance/Modifier.cs b/mono/Assistance/Modifier.cs
index c3c72bd..9bc55d0 100644
--- a/mono/Assistance/Modifier.cs
+++ b/mono/Assistance/Modifier.cs
@@ -24,6 +24,9 @@ namespace Assistance {
 		public virtual void modify(Track track, InputManager.KeyPoint keyPoint, List<Track> outTracks) { }
 		public virtual void modify(List<Track> tracks, InputManager.KeyPoint keyPoint, List<Track> outTracks)
 			{ foreach(Track track in tracks) modify(track, keyPoint, outTracks); }
+		public virtual void draw(Cairo.Context context, Track track) { }
+		public virtual void draw(Cairo.Context context, List<Track> tracks)
+			{ foreach(Track track in tracks) draw(context, track); }
 		public virtual void deactivate() { }
 
 		public virtual void draw(Cairo.Context context) { }
diff --git a/mono/Assistance/ModifierSnowflake.cs b/mono/Assistance/ModifierSnowflake.cs
index 881794e..d174366 100644
--- a/mono/Assistance/ModifierSnowflake.cs
+++ b/mono/Assistance/ModifierSnowflake.cs
@@ -77,7 +77,6 @@ namespace Assistance {
 				}
 				
 				// add points
-				Track.WayPoint p0 = track.getWayPoint(start - 1);
 				for(int i = start; i < track.points.Count; ++i)
 					subTrack.points.Add( subTrack.calcWayPoint(i) );
 				subTrack.wayPointsAdded += subTrack.points.Count - start;
diff --git a/mono/Assistance/ToolFull.cs b/mono/Assistance/ToolFull.cs
new file mode 100644
index 0000000..d9ea6b1
--- /dev/null
+++ b/mono/Assistance/ToolFull.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+
+namespace Assistance {
+	public class ToolFull: Tool {
+		public static readonly Drawing.Pen pen = new Drawing.Pen("Dark Green", 10.0);
+		public static readonly double levelAlpha = 0.8;
+	
+		
+		private readonly List<DynamicSurface> stack = new List<DynamicSurface>();
+
+		
+		public ToolFull(Workarea workarea): base(workarea) { }
+		
+		public override ModifierTypes getAvailableModifierTypes()
+			{ return ModifierTypes.All; }
+		
+		private DynamicSurface getSurface(int level)
+			{ return level > 0 ? stack[level - 1] : workarea.document.canvas; }
+		private DynamicSurface getSurface()
+			{ return getSurface(stack.Count); }
+		
+		public override bool paintPush() {
+			stack.Add(new DynamicSurface());
+			return true;
+		}
+
+		private void paintPoint(DynamicSurface surface, Cairo.Context context, Track.WayPoint point) {
+			Point p = point.point.position;
+			double r = pen.width*point.point.pressure;
+			double rr = r + 1.0;
+			
+			surface.expandContext(ref context, new Rectangle(p.x - rr, p.y - rr, p.x + rr, p.y + rr));
+
+			context.Save();
+			pen.apply(context);
+			context.Arc(p.x, p.y, r, 0.0, 2.0*Math.PI);
+			context.Fill();
+			context.Restore();
+		}
+
+		public override void paintTracks(List<Track> tracks) {
+			DynamicSurface surface = getSurface();
+			Cairo.Context context = surface.getContext();
+			while(true) {
+				Track track = null;
+				long minTicks = long.MaxValue;
+				double minTimeOffset = 0.0;
+				foreach(Track t in tracks) {
+					if (t.wayPointsAdded > 0) {
+						long ticks = t.ticks;
+						double timeOffset = t.timeOffset + t.points[t.points.Count - t.wayPointsAdded].time;
+						if ((ticks - minTicks)*Timer.frequency + timeOffset - minTimeOffset < 0.0) {
+							track = t;
+							minTicks = ticks;
+							minTimeOffset = timeOffset;
+						}
+					}
+				}
+				if (track == null) break;
+				paintPoint(surface, context, track.points[track.points.Count - track.wayPointsAdded]);
+				--track.wayPointsAdded;
+			}
+			context.GetTarget().Flush();
+			context.Dispose();
+		}
+		
+		public override int paintApply(int count) {
+			int level = stack.Count - count;
+			DynamicSurface surface = getSurface(level);
+			Cairo.Context context = surface.getContext();
+			for(int i = level; i < stack.Count; ++i) {
+				surface.expandContext(ref context, stack[i].getBounds(), true);
+				stack[i].draw(context);
+			}
+			context.GetTarget().Flush();
+			context.Dispose();
+			paintPop(count);
+			return count;
+		}
+		
+		public override void paintCancel()
+			{ getSurface().clear(); }
+		
+		public override void paintPop(int count) {
+			int level = stack.Count - count;
+			for(int i = stack.Count - 1; i >= level; --i)
+				stack[i].Dispose();
+			stack.RemoveRange(level, count);
+		}
+
+		public override void draw(Cairo.Context context) {
+			double alpha = levelAlpha;
+			foreach(DynamicSurface surface in stack)
+				{ surface.draw(context, alpha); alpha *= levelAlpha; }
+		}
+	}
+}
\ No newline at end of file
diff --git a/mono/Assistance/Workarea.cs b/mono/Assistance/Workarea.cs
index 90ced8b..4850dff 100644
--- a/mono/Assistance/Workarea.cs
+++ b/mono/Assistance/Workarea.cs
@@ -5,9 +5,47 @@ using System.Linq;
 namespace Assistance {
 	public class Workarea {
 		public readonly Document document;
+		public readonly InputManager inputManager;
+		
+		private readonly InputModifierTangents modifierTangents;
+		private readonly InputModifierAssistants modifierAssistantsSimple;
+		private readonly InputModifierAssistants modifierAssistantsTangents;
+		private readonly InputModifierInterpolation modifierInterpolation;
 		
 		public Workarea() {
 			document = new Document(this);
+			inputManager = new InputManager(this);
+			
+			modifierTangents = new InputModifierTangents();
+			modifierAssistantsSimple = new InputModifierAssistants(this, false);
+			modifierAssistantsTangents = new InputModifierAssistants(this, true);
+			modifierInterpolation = new InputModifierInterpolation();
+		}
+		
+		public Tool getTool()
+			{ return inputManager.getTool(); }
+			
+		public void setTool(Tool tool, bool force = false) {
+			if (getTool() != tool || force) {
+				inputManager.deactivate();
+				inputManager.clearModifiers();
+				if (tool != null) {
+					Tool.ModifierTypes types = tool.getAvailableModifierTypes();
+					if ((Tool.ModifierTypes.Tangents & types) != 0 && (Tool.ModifierTypes.Guideline & types) == 0)
+						inputManager.addModifier(modifierTangents);
+					if ((Tool.ModifierTypes.Tangents & types) == 0 && (Tool.ModifierTypes.Guideline & types) != 0)
+						inputManager.addModifier(modifierAssistantsSimple);
+					if ((Tool.ModifierTypes.Tangents & types) != 0 && (Tool.ModifierTypes.Guideline & types) != 0)
+						inputManager.addModifier(modifierAssistantsTangents);
+					if ((Tool.ModifierTypes.Multiline & types) != 0)
+						foreach(Modifier modifier in document.modifiers)
+							inputManager.addModifier(modifier);
+					if ((Tool.ModifierTypes.Interpolation & types) != 0)
+						inputManager.addModifier(modifierInterpolation);
+				}
+				inputManager.setTool(tool);
+				inputManager.activate();
+			}
 		}
 
 		public ActivePoint findPoint(Point position) {
@@ -22,32 +60,12 @@ namespace Assistance {
 				assistant.getGuidelines(outGuidelines, target);
 		}
 
-		public void draw(Cairo.Context context, ActivePoint activePoint, Point target, Track track) {
+		public void draw(Cairo.Context context, ActivePoint activePoint) {
 			// canvas
 			document.canvas.draw(context);
 
-			// guidelines and track
-			List<Guideline> guidelines = new List<Guideline>();
-			if (track != null && track.points.Count > 0) {
-				Guideline guideline;
-				Track modifiedTrack = modifyTrackByAssistant(track, out guideline);
-				
-				getGuidelines(guidelines, modifiedTrack.transform(track.points.Last()).point);
-				foreach(Guideline gl in guidelines)
-					gl.draw(context);
-
-				track.draw(context, true);
-				if (guideline != null) guideline.draw(context, true);
-				
-				List<Track> modifiedTracks = modifyTrackByModifiers(modifiedTrack);
-				rebuildTracks(modifiedTracks);
-				foreach(Track t in modifiedTracks)
-					t.draw(context);
-			} else {
-				getGuidelines(guidelines, target);
-				foreach(Guideline guideline in guidelines)
-					guideline.draw(context);
-			}
+			// input manager
+			inputManager.draw(context);
 			
 			// modifiers
 			foreach(Modifier modifier in document.modifiers)
@@ -61,45 +79,6 @@ namespace Assistance {
 			foreach(ActivePoint point in document.points)
 				point.draw(context, activePoint == point);
 		}
-	
-		public Track modifyTrackByAssistant(Track track, out Guideline guideline) {
-			guideline = null;
-			if (track.points.Count < 1)
-				return track.createChild(Geometry.noTransform);
-
-			List<Guideline> guidelines = new List<Guideline>();
-			getGuidelines(guidelines, track.points[0].point);
-			guideline = Guideline.findBest(guidelines, track);
-			if (guideline == null)
-				return track.createChild(Geometry.noTransform);
-			return track.createChild(guideline.transformPoint);
-		}
-		
-		public List<Track> modifyTrackByModifiers(Track track) {
-			List<Track> tracks = new List<Track>() { track };
-			foreach(Modifier modifier in document.modifiers)
-				tracks = modifier.modify(tracks);
-			return tracks;
-		}
-
-		public void rebuildTracks(List<Track> tracks) {
-			foreach(Track track in tracks)
-				track.rebuild();
-		}
-
-		public List<Track> modifyTrack(Track track) {
-			Guideline guideline;
-			List<Track> tracks = modifyTrackByModifiers(
-				modifyTrackByAssistant(track, out guideline) );
-			rebuildTracks(tracks);
-			return tracks;
-		}
-				
-		public void paintTrack(Track track) {
-			List<Track> tracks = modifyTrack(track);
-			foreach(Track t in tracks)
-				document.canvas.paintTrack(t);
-		}
 	}
 }