using PaintDotNet.Measurement.HistoryFunctions; using PaintDotNet.Measurement.HistoryMementos; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Runtime.Serialization; using System.Text; using System.Windows.Forms; namespace PaintDotNet.Measurement.Tools { public abstract class MoveToolBase : Tool { protected Cursor moveToolCursor; protected bool dontDrop = false; // so that OnSelectionChanging() can tell who is raising the event ... don't drop the pixels if WE caused the event protected float angleDelta; protected MoveNubRenderer[] moveNubs; protected RotateNubRenderer rotateNub; protected bool tracking; protected Context context; protected bool hostShouldShowAngle; protected float hostAngle; protected List currentHistoryMementos = new List(); protected bool deactivateOnLayerChange = true; protected bool enableOutline = true; public override bool DeactivateOnLayerChange { get { return this.deactivateOnLayerChange; } } protected enum Mode { Translate, Scale, Rotate } // Corresponds to array positions in this.moveNubs for easy mapping between the two protected enum Edge { TopLeft = 0, Top = 1, TopRight = 2, Right = 3, BottomRight = 4, Bottom = 5, BottomLeft = 6, Left = 7, None = 99 } [Serializable] protected class Context : ICloneable, ISerializable, IDisposable { public bool lifted; public Guid seriesGuid; public Matrix baseTransform; // a copy of the selection's interim transform at the time of mouse-down public Matrix liftTransform; // a copy of the selection's interim transform at the time of lifting public Matrix deltaTransform; // the transformations made since lifting public RectangleF liftedBounds; public RectangleF startBounds; public float startAngle; public PdnGraphicsPath startPath; public Mode currentMode; public Edge startEdge; public Point startMouseXY; public Point offset; private float[] GetMatrixElements(Matrix m) { if (m == null) { return null; } else { return m.Elements; } } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("lifted", this.lifted); info.AddValue("seriesGuid", this.seriesGuid); info.AddValue("baseTransform", GetMatrixElements(this.baseTransform)); info.AddValue("deltaTransform", GetMatrixElements(this.deltaTransform)); info.AddValue("liftTransform", GetMatrixElements(this.liftTransform)); info.AddValue("liftedBounds", this.liftedBounds); info.AddValue("startBounds", this.startBounds); info.AddValue("startAngle", this.startAngle); info.AddValue("startPath", this.startPath); info.AddValue("currentMode", this.currentMode); info.AddValue("startEdge", this.startEdge); info.AddValue("startMouseXY", this.startMouseXY); info.AddValue("offset", this.offset); } private Matrix ReadMatrix(SerializationInfo info, StreamingContext context, string name) { Matrix m; float[] e = (float[])info.GetValue(name, typeof(float[])); if (e == null) { m = null; } else { m = new Matrix(e[0], e[1], e[2], e[3], e[4], e[5]); } return m; } public Context(SerializationInfo info, StreamingContext context) { this.lifted = (bool)info.GetValue("lifted", typeof(bool)); this.seriesGuid = (Guid)info.GetValue("seriesGuid", typeof(Guid)); this.baseTransform = ReadMatrix(info, context, "baseTransform"); this.deltaTransform = ReadMatrix(info, context, "deltaTransform"); this.liftTransform = ReadMatrix(info, context, "liftTransform"); this.liftedBounds = (RectangleF)info.GetValue("liftedBounds", typeof(RectangleF)); this.startBounds = (RectangleF)info.GetValue("startBounds", typeof(RectangleF)); this.startAngle = (float)info.GetValue("startAngle", typeof(float)); this.startPath = (PdnGraphicsPath)info.GetValue("startPath", typeof(PdnGraphicsPath)); this.currentMode = (Mode)info.GetValue("currentMode", typeof(Mode)); this.startEdge = (Edge)info.GetValue("startEdge", typeof(Edge)); this.startMouseXY = (Point)info.GetValue("startMouseXY", typeof(Point)); this.offset = (Point)info.GetValue("offset", typeof(Point)); } public Context() { } public Context(Context cloneMe) { this.lifted = cloneMe.lifted; this.seriesGuid = cloneMe.seriesGuid; if (cloneMe.baseTransform != null) { this.baseTransform = cloneMe.baseTransform.Clone(); } if (cloneMe.deltaTransform != null) { this.deltaTransform = cloneMe.deltaTransform.Clone(); } if (cloneMe.liftTransform != null) { this.liftTransform = cloneMe.liftTransform.Clone(); } this.liftedBounds = cloneMe.liftedBounds; this.startBounds = cloneMe.startBounds; this.startAngle = cloneMe.startAngle; if (cloneMe.startPath != null) { this.startPath = cloneMe.startPath.Clone(); } this.currentMode = cloneMe.currentMode; this.startEdge = cloneMe.startEdge; this.startMouseXY = cloneMe.startMouseXY; this.offset = cloneMe.offset; } ~Context() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (this.baseTransform != null) { this.baseTransform.Dispose(); this.baseTransform = null; } if (this.deltaTransform != null) { this.deltaTransform.Dispose(); this.deltaTransform = null; } if (this.liftTransform != null) { this.liftTransform.Dispose(); this.liftTransform = null; } if (this.startPath != null) { this.startPath.Dispose(); this.startPath = null; } } } public virtual object Clone() { return new Context(this); } } protected class CompoundToolHistoryMemento : ToolHistoryMemento { private CompoundHistoryMemento compoundHistoryMemento; public CompoundHistoryMemento CompoundHistoryMemento { get { return this.compoundHistoryMemento; } } protected override HistoryMemento OnToolUndo() { CompoundHistoryMemento chm = (CompoundHistoryMemento)this.compoundHistoryMemento.PerformUndo(); CompoundToolHistoryMemento cthm = new CompoundToolHistoryMemento(chm, DocumentWorkspace, this.Name, this.Image); return cthm; } public CompoundToolHistoryMemento(CompoundHistoryMemento chm, IDocumentWorkspace documentWorkspace, string name, ImageResource image) : base(documentWorkspace, name, image) { this.compoundHistoryMemento = chm; } } public bool HostShouldShowAngle { get { return this.hostShouldShowAngle; } } public float HostAngle { get { return this.hostAngle; } } protected void DestroyNubs() { if (this.moveNubs != null) { for (int i = 0; i < this.moveNubs.Length; ++i) { this.RendererList.Remove(this.moveNubs[i]); this.moveNubs[i].Dispose(); this.moveNubs[i] = null; } this.moveNubs = null; } if (this.rotateNub != null) { this.RendererList.Remove(this.rotateNub); this.rotateNub.Dispose(); this.rotateNub = null; } } protected PointF GetEdgeVector(Edge edge) { PointF u; switch (edge) { case Edge.TopLeft: u = new PointF(-1, -1); break; case Edge.Top: u = new PointF(0, -1); break; case Edge.TopRight: u = new PointF(1, -1); break; case Edge.Left: u = new PointF(-1, 0); break; case Edge.Right: u = new PointF(1, 0); break; case Edge.BottomLeft: u = new PointF(-1, 1); break; case Edge.BottomRight: u = new PointF(1, 1); break; case Edge.Bottom: u = new PointF(0, 1); break; default: throw new InvalidEnumArgumentException(); } return u; } protected void DetermineMoveMode(MouseEventArgs e, out Mode mode, out Edge edge) { mode = Mode.Translate; edge = Edge.None; if (e.Button == MouseButtons.Right) { mode = Mode.Rotate; } else { float minDistance = float.MaxValue; Point mousePt = new Point(e.X, e.Y); for (int i = 0; i < this.moveNubs.Length; ++i) { MoveNubRenderer nub = this.moveNubs[i]; if (nub.IsPointTouching(mousePt, true)) { float distance = Utility.Distance((PointF)mousePt, nub.Location); if (distance < minDistance) { minDistance = distance; mode = Mode.Scale; edge = (Edge)i; } } } } return; } protected override void OnPulse() { if (this.moveNubs != null) { for (int i = 0; i < this.moveNubs.Length; ++i) { // Oscillate between 25% and 100% alpha over a period of 2 seconds // Alpha value of 100% is sustained for a large duration of this period const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per period long tick = (DateTime.Now.Ticks % period) + (i * (period / this.moveNubs.Length)); double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI)); // sin is [-1, +1] sin = Math.Min(0.5, sin); // sin is [-1, +0.5] sin += 1.0; // sin is [0, 1.5] sin /= 2.0; // sin is [0, 0.75] sin += 0.25; // sin is [0.25, 1] int newAlpha = (int)(sin * 255.0); int clampedAlpha = Utility.Clamp(newAlpha, 0, 255); this.moveNubs[i].Alpha = clampedAlpha; } } base.OnPulse(); } protected void PositionNubs(Mode currentMode) { if (this.moveNubs == null) { this.moveNubs = new MoveNubRenderer[8]; for (int i = 0; i < this.moveNubs.Length; ++i) { this.moveNubs[i] = new MoveNubRenderer(this.RendererList); this.RendererList.Add(this.moveNubs[i], false); } RectangleF bounds = Selection.GetBoundsF(false); this.moveNubs[(int)Edge.TopLeft].Location = new PointF(bounds.Left, bounds.Top); this.moveNubs[(int)Edge.TopLeft].Shape = MoveNubShape.Circle; this.moveNubs[(int)Edge.Top].Location = new PointF((bounds.Left + bounds.Right) / 2.0f, bounds.Top); this.moveNubs[(int)Edge.TopRight].Location = new PointF(bounds.Right, bounds.Top); this.moveNubs[(int)Edge.TopRight].Shape = MoveNubShape.Circle; this.moveNubs[(int)Edge.Left].Location = new PointF(bounds.Left, (bounds.Top + bounds.Bottom) / 2.0f); this.moveNubs[(int)Edge.Right].Location = new PointF(bounds.Right, (bounds.Top + bounds.Bottom) / 2.0f); this.moveNubs[(int)Edge.BottomLeft].Location = new PointF(bounds.Left, bounds.Bottom); this.moveNubs[(int)Edge.BottomLeft].Shape = MoveNubShape.Circle; this.moveNubs[(int)Edge.Bottom].Location = new PointF((bounds.Left + bounds.Right) / 2.0f, bounds.Bottom); this.moveNubs[(int)Edge.BottomRight].Location = new PointF(bounds.Right, bounds.Bottom); this.moveNubs[(int)Edge.BottomRight].Shape = MoveNubShape.Circle; } if (this.rotateNub == null) { this.rotateNub = new RotateNubRenderer(this.RendererList); rotateNub.Visible = false; this.RendererList.Add(this.rotateNub, false); } if (Selection.IsEmpty) { foreach (SurfaceBoxRenderer nub in this.moveNubs) { nub.Visible = false; } this.rotateNub.Visible = false; } else { foreach (MoveNubRenderer nub in this.moveNubs) { nub.Visible = !tracking || currentMode == Mode.Scale; nub.Transform = Selection.GetInterimTransformReadOnly(); } } } protected void HideNubs() { if (this.moveNubs != null) { foreach (SurfaceBoxRenderer sbr in this.moveNubs) { sbr.Visible = false; } } if (this.rotateNub != null) { this.rotateNub.Visible = false; } } protected Edge FlipEdgeVertically(Edge flipMe) { Edge flippedEdge; switch (flipMe) { default: throw new InvalidEnumArgumentException(); case Edge.Bottom: flippedEdge = Edge.Top; break; case Edge.BottomLeft: flippedEdge = Edge.TopLeft; break; case Edge.BottomRight: flippedEdge = Edge.TopRight; break; case Edge.Left: flippedEdge = Edge.Left; break; case Edge.None: flippedEdge = Edge.None; break; case Edge.Right: flippedEdge = Edge.Right; break; case Edge.Top: flippedEdge = Edge.Bottom; break; case Edge.TopLeft: flippedEdge = Edge.BottomLeft; break; case Edge.TopRight: flippedEdge = Edge.BottomRight; break; } return flippedEdge; } // Constrains the given width and height to the aspect ratio of this.liftedBounds protected void ConstrainScaling(RectangleF liftedBounds, float startWidth, float startHeight, float newWidth, float newHeight, out float newXScale, out float newYScale) { float hRatio = newWidth / (float)liftedBounds.Width; float vRatio = newHeight / (float)liftedBounds.Height; float bestScale = Math.Min(hRatio, vRatio); float bestWidth = (float)liftedBounds.Width * bestScale; float bestHeight = (float)liftedBounds.Height * bestScale; newXScale = bestWidth / startWidth; newYScale = bestHeight / startHeight; } // Constrains to nearest 15 degree angle protected float ConstrainAngle(float angle) { while (angle < 0) { angle += 360.0f; } int iangle = (int)angle; int lowerBound = (iangle / 15) * 15; int upperBound = lowerBound + 15; float lowerDiff = Math.Abs(angle - (float)lowerBound); float upperDiff = Math.Abs(angle - (float)upperBound); float newAngle; if (lowerDiff < upperDiff) { newAngle = (float)lowerBound; } else { newAngle = (float)upperBound; } if (newAngle > 180.0f) { newAngle -= 360.0f; } return newAngle; } protected override void OnKeyPress(Keys key) { if (!tracking) { int dx = 0; int dy = 0; if ((key & Keys.KeyCode) == Keys.Left) { dx = -1; } else if ((key & Keys.KeyCode) == Keys.Right) { dx = +1; } else if ((key & Keys.KeyCode) == Keys.Up) { dy = -1; } else if ((key & Keys.KeyCode) == Keys.Down) { dy = +1; } if ((key & Keys.Control) != Keys.None) { dx *= 10; dy *= 10; } // Simulate moving the selection if (dx != 0 || dy != 0) { Point pos = Cursor.Position; Point docPos = new Point(-70000, -70000); Point newDocPos = new Point(docPos.X + dx, docPos.Y + dy); OnMouseDown(new MouseEventArgs(MouseButtons.Left, 0, docPos.X, docPos.Y, 0)); OnMouseMove(new MouseEventArgs(MouseButtons.Left, 0, newDocPos.X, newDocPos.Y, 0)); OnMouseUp(new MouseEventArgs(MouseButtons.Left, 0, newDocPos.X, newDocPos.Y, 0)); } } else { base.OnKeyPress(key); } } protected abstract void OnLift(MouseEventArgs e); protected abstract void Drop(); protected abstract void PreRender(); protected abstract void Render(Point newOffset, bool useNewOffset); protected abstract void PushContextHistoryMemento(); protected void Lift(MouseEventArgs e) { this.PushContextHistoryMemento(); this.context.seriesGuid = Guid.NewGuid(); DetermineMoveMode(e, out this.context.currentMode, out this.context.startEdge); // lift! this.context.startBounds = this.context.liftedBounds; this.context.liftedBounds = Selection.GetBoundsF(false); this.context.startMouseXY = new Point(e.X, e.Y); this.context.offset = new Point(0, 0); this.context.startAngle = 0.0f; this.context.lifted = true; this.context.liftTransform = Selection.GetCumulativeTransformCopy(); OnLift(e); PositionNubs(this.context.currentMode); } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (tracking) { return; } bool determinedMoveMode = false; Mode newMode = Mode.Translate; Edge newEdge = Edge.None; if (Selection.IsEmpty) { SelectionHistoryMemento shm = new SelectionHistoryMemento( SelectAllFunction.StaticName, PdnResources.GetImageResource("Icons.MenuEditSelectAllIcon.png"), DocumentWorkspace); DocumentWorkspace.GetSelection().PerformChanging(); DocumentWorkspace.GetSelection().Reset(); DocumentWorkspace.GetSelection().SetContinuation(Document.Bounds, CombineMode.Replace); DocumentWorkspace.GetSelection().CommitContinuation(); DocumentWorkspace.GetSelection().PerformChanged(); if (e.Button == MouseButtons.Right) { newMode = Mode.Rotate; } else { newMode = Mode.Translate; } newEdge = Edge.None; determinedMoveMode = true; } DocumentWorkspace.SetEnableSelectionOutline(this.enableOutline); if (!context.lifted) { Lift(e); } PushContextHistoryMemento(); if (!determinedMoveMode) { DetermineMoveMode(e, out newMode, out newEdge); determinedMoveMode = true; } if (this.context.deltaTransform != null) { this.context.deltaTransform.Dispose(); this.context.deltaTransform = null; } this.context.deltaTransform = new Matrix(); this.context.deltaTransform.Reset(); if (newMode == Mode.Translate || newMode == Mode.Scale || newMode != this.context.currentMode || newMode == Mode.Rotate) { this.context.startBounds = Selection.GetBoundsF(); this.context.startMouseXY = new Point(e.X, e.Y); this.context.offset = new Point(0, 0); if (this.context.baseTransform != null) { this.context.baseTransform.Dispose(); this.context.baseTransform = null; } this.context.baseTransform = Selection.GetInterimTransformCopy(); } this.context.startEdge = newEdge; this.context.currentMode = newMode; PositionNubs(this.context.currentMode); tracking = true; this.rotateNub.Visible = (this.context.currentMode == Mode.Rotate); if (this.context.startPath != null) { this.context.startPath.Dispose(); this.context.startPath = null; } this.context.startPath = Selection.CreatePath(); this.context.startAngle = Utility.GetAngleOfTransform(Selection.GetInterimTransformReadOnly()); SelectionHistoryMemento sha1 = new SelectionHistoryMemento(this.Name, this.Image, this.DocumentWorkspace); this.currentHistoryMementos.Add(sha1); OnMouseMove(e); if (this.enableOutline) { DocumentWorkspace.ResetOutlineWhiteOpacity(); } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); StringBuilder sbLogger = new StringBuilder(); try { OnMouseMoveImpl(e, sbLogger); } catch (Exception ex) { throw new ApplicationException("Tracing data: " + sbLogger.ToString(), ex); } } private void OnMouseMoveImpl(MouseEventArgs e, StringBuilder sbLogger) { if (!this.tracking) { sbLogger.Append("1 "); Cursor cursor = this.moveToolCursor; for (int i = 0; i < this.moveNubs.Length; ++i) { sbLogger.Append("2 "); MoveNubRenderer nub = this.moveNubs[i]; sbLogger.Append("3 "); if (nub.Visible && nub.IsPointTouching(new Point(e.X, e.Y), true)) { sbLogger.Append("4 "); cursor = this.handCursor; break; } } this.Cursor = cursor; sbLogger.Append("5 "); } else { sbLogger.Append("6 "); if (this.context.currentMode != Mode.Translate) { sbLogger.Append("7 "); this.Cursor = this.handCursorMouseDown; } sbLogger.Append("8 "); Point newMouseXY = new Point(e.X, e.Y); Point newOffset = new Point(newMouseXY.X - context.startMouseXY.X, newMouseXY.Y - context.startMouseXY.Y); PreRender(); this.dontDrop = true; sbLogger.Append("9 "); Selection.PerformChanging(); using (Matrix translateMatrix = new Matrix()) { RectangleF rect; translateMatrix.Reset(); if (this.context.baseTransform != null) { Selection.SetInterimTransform(this.context.baseTransform); } Matrix interim = Selection.GetInterimTransformCopy(); switch (this.context.currentMode) { case Mode.Translate: translateMatrix.Translate((float)newOffset.X, (float)newOffset.Y, MatrixOrder.Append); break; case Mode.Rotate: rect = this.context.liftedBounds; PointF center = new PointF(rect.X + (rect.Width / 2.0f), rect.Y + (rect.Height / 2.0f)); center = Utility.TransformOnePoint(interim, center); double theta1 = Math.Atan2(context.startMouseXY.Y - center.Y, context.startMouseXY.X - center.X); double theta2 = Math.Atan2(e.Y - center.Y, e.X - center.X); double thetaDelta = theta2 - theta1; this.angleDelta = (float)(thetaDelta * (180.0f / Math.PI)); float angle = this.context.startAngle + this.angleDelta; if ((ModifierKeys & Keys.Shift) != 0) { angle = ConstrainAngle(angle); angleDelta = angle - this.context.startAngle; } translateMatrix.RotateAt(angleDelta, center, MatrixOrder.Append); this.rotateNub.Location = center; this.rotateNub.Angle = this.context.startAngle + angleDelta; break; case Mode.Scale: PointF xyAxes = GetEdgeVector(this.context.startEdge); PointF xAxis = new PointF(xyAxes.X, 0); PointF yAxis = new PointF(0, xyAxes.Y); PointF edgeX = Utility.TransformOneVector(interim, xAxis); PointF edgeY = Utility.TransformOneVector(interim, yAxis); PointF edgeXN = Utility.NormalizeVector2(edgeX); PointF edgeYN = Utility.NormalizeVector2(edgeY); PointF xu; float xulen; PointF xv; Utility.GetProjection((PointF)newOffset, edgeXN, out xu, out xulen, out xv); PointF yu; float yulen; PointF yv; Utility.GetProjection((PointF)newOffset, edgeYN, out yu, out yulen, out yv); PdnGraphicsPath startPath2 = this.context.startPath.Clone(); RectangleF sp2Bounds = startPath2.GetBounds(); PointF sp2BoundsCenter = new PointF((sp2Bounds.Left + sp2Bounds.Right) / 2.0f, (sp2Bounds.Top + sp2Bounds.Bottom) / 2.0f); float tAngle = Utility.GetAngleOfTransform(interim); bool isFlipped = Utility.IsTransformFlipped(interim); using (Matrix spm = new Matrix()) { spm.Reset(); spm.RotateAt(-tAngle, sp2BoundsCenter, MatrixOrder.Append); translateMatrix.RotateAt(-tAngle, sp2BoundsCenter, MatrixOrder.Append); startPath2.Transform(spm); } RectangleF spBounds2 = startPath2.GetBounds(); startPath2.Dispose(); startPath2 = null; float xTranslate; float yTranslate; bool allowConstrain; Edge theEdge = this.context.startEdge; // If the transform is flipped, then GetTransformAngle will return 180 degrees // even though no rotation has actually taken place. Thus we have to scratch // our head and go "hmm, let's make some adjustments to this." Otherwise stretching // the top and bottom nubs goes in the wrong direction. if (isFlipped) { theEdge = FlipEdgeVertically(theEdge); } switch (theEdge) { default: throw new InvalidEnumArgumentException(); case Edge.TopLeft: allowConstrain = true; xTranslate = -spBounds2.X - spBounds2.Width; yTranslate = -spBounds2.Y - spBounds2.Height; break; case Edge.Top: allowConstrain = false; xTranslate = 0; yTranslate = -spBounds2.Y - spBounds2.Height; break; case Edge.TopRight: allowConstrain = true; xTranslate = -spBounds2.X; yTranslate = -spBounds2.Y - spBounds2.Height; break; case Edge.Left: allowConstrain = false; xTranslate = -spBounds2.X - spBounds2.Width; yTranslate = 0; break; case Edge.Right: allowConstrain = false; xTranslate = -spBounds2.X; yTranslate = 0; break; case Edge.BottomLeft: allowConstrain = true; xTranslate = -spBounds2.X - spBounds2.Width; yTranslate = -spBounds2.Y; break; case Edge.Bottom: allowConstrain = false; xTranslate = 0; yTranslate = -spBounds2.Y; break; case Edge.BottomRight: allowConstrain = true; xTranslate = -spBounds2.X; yTranslate = -spBounds2.Y; break; } translateMatrix.Translate(xTranslate, yTranslate, MatrixOrder.Append); float newWidth = spBounds2.Width + xulen; float newHeight = spBounds2.Height + yulen; float xScale = newWidth / spBounds2.Width; float yScale = newHeight / spBounds2.Height; if (allowConstrain && (this.ModifierKeys & Keys.Shift) != 0) { ConstrainScaling(this.context.liftedBounds, spBounds2.Width, spBounds2.Height, newWidth, newHeight, out xScale, out yScale); } translateMatrix.Scale(xScale, yScale, MatrixOrder.Append); translateMatrix.Translate(-xTranslate, -yTranslate, MatrixOrder.Append); translateMatrix.RotateAt(+tAngle, sp2BoundsCenter, MatrixOrder.Append); break; default: throw new InvalidEnumArgumentException(); } this.context.deltaTransform.Reset(); this.context.deltaTransform.Multiply(this.context.liftTransform, MatrixOrder.Append); this.context.deltaTransform.Multiply(translateMatrix, MatrixOrder.Append); translateMatrix.Multiply(this.context.baseTransform, MatrixOrder.Prepend); Selection.SetInterimTransform(translateMatrix); interim.Dispose(); interim = null; } // advertise our angle of rotation to any host (i.e. mainform) that might want to use that information this.hostShouldShowAngle = this.rotateNub.Visible; this.hostAngle = -this.rotateNub.Angle; Selection.PerformChanged(); dontDrop = false; Render(newOffset, true); Update(); sbLogger.Append("a "); this.context.offset = newOffset; sbLogger.Append("b "); if (this.enableOutline) { DocumentWorkspace.ResetOutlineWhiteOpacity(); } sbLogger.Append("c "); } sbLogger.Append("d "); } protected override void OnMouseUp(MouseEventArgs e) { DocumentWorkspace.SetEnableSelectionOutline(true); base.OnMouseUp(e); } public MoveToolBase(IDocumentWorkspace documentWorkspace, ImageResource toolBarImage, string name, string helpText, char hotKey, bool skipIfActiveOnHotKey, ToolBarConfigItems toolBarConfigItems) : base(documentWorkspace, toolBarImage, name, helpText, hotKey, skipIfActiveOnHotKey, toolBarConfigItems) { } } }