| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622 | using PaintDotNet.Measurement.Enum;using PaintDotNet.Measurement.HistoryMementos;using System;using System.Collections;using System.Collections.Generic;using System.ComponentModel;using System.Drawing;using System.Windows.Forms;namespace PaintDotNet.Measurement.Tools{    public class TextTool : Tool    {        private enum EditingMode        {            NotEditing,            EmptyEdit,            Editing        }        private string statusBarTextFormat = PdnResources.GetString("TextTool.StatusText.TextInfo.Format");        private Point startMouseXY;        private Point startClickPoint;        private bool tracking;        private MoveNubRenderer moveNub;        private int ignoreRedraw;        private RenderArgs ra;        private EditingMode mode;        private ArrayList lines;        private int linePos;        private int textPos;        private Point clickPoint;        private Font font;        private TextAlignment alignment;        private IrregularSurface saved;        private const int cursorInterval = 300;        private bool pulseEnabled;        private System.DateTime startTime;        private bool lastPulseCursorState;        private Cursor textToolCursor;        private PaintDotNet.Threading.ThreadPool threadPool;        private bool enableNub = true;        private CompoundHistoryMemento currentHA;        private bool controlKeyDown = false;        private DateTime controlKeyDownTime = DateTime.MinValue;        private readonly TimeSpan controlKeyDownThreshold = new TimeSpan(0, 0, 0, 0, 400);        private void AlphaBlendingChangedHandler(object sender, EventArgs e)        {            if (mode != EditingMode.NotEditing)            {                RedrawText(true);            }        }        private EventHandler fontChangedDelegate;        private void FontChangedHandler(object sender, EventArgs a)        {            font = AppEnvironment.FontInfo().CreateFont();            if (mode != EditingMode.NotEditing)            {                this.sizes = null;                RedrawText(true);            }        }        private EventHandler fontSmoothingChangedDelegate;        private void FontSmoothingChangedHandler(object sender, EventArgs e)        {            if (mode != EditingMode.NotEditing)            {                this.sizes = null;                RedrawText(true);            }        }        private EventHandler alignmentChangedDelegate;        private void AlignmentChangedHandler(object sender, EventArgs a)        {            alignment = AppEnvironment.TextAlignment();            if (mode != EditingMode.NotEditing)            {                this.sizes = null;                RedrawText(true);            }        }        private EventHandler brushChangedDelegate;        private void BrushChangedHandler(object sender, EventArgs a)        {            if (mode != EditingMode.NotEditing)            {                RedrawText(true);            }        }        private EventHandler antiAliasChangedDelegate;        private void AntiAliasChangedHandler(object sender, EventArgs a)        {            if (mode != EditingMode.NotEditing)            {                this.sizes = null;                RedrawText(true);            }        }        private EventHandler foreColorChangedDelegate;        private void ForeColorChangedHandler(object sender, EventArgs e)        {            if (mode != EditingMode.NotEditing)            {                RedrawText(true);            }        }        private void BackColorChangedHandler(object sender, EventArgs e)        {            if (mode != EditingMode.NotEditing)            {                RedrawText(true);            }        }        private bool OnBackspaceTyped(Keys keys)        {            if (!this.DocumentWorkspace.Visible())            {                return false;            }            else if (this.mode != EditingMode.NotEditing)            {                OnKeyPress(Keys.Back);                return true;            }            else            {                return false;            }        }        protected override void OnActivate()        {            PdnBaseForm.RegisterFormHotKey(Keys.Back, OnBackspaceTyped);            base.OnActivate();            this.textToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.TextToolCursor.cur"));            this.Cursor = this.textToolCursor;            fontChangedDelegate = new EventHandler(FontChangedHandler);            fontSmoothingChangedDelegate = new EventHandler(FontSmoothingChangedHandler);            alignmentChangedDelegate = new EventHandler(AlignmentChangedHandler);            brushChangedDelegate = new EventHandler(BrushChangedHandler);            antiAliasChangedDelegate = new EventHandler(AntiAliasChangedHandler);            foreColorChangedDelegate = new EventHandler(ForeColorChangedHandler);            ra = new RenderArgs(((BitmapLayer)ActiveLayer).Surface);            mode = EditingMode.NotEditing;            font = AppEnvironment.FontInfo().CreateFont();            alignment = AppEnvironment.TextAlignment();            AppEnvironment.BrushInfoChanged += brushChangedDelegate;            AppEnvironment.FontInfoChanged += fontChangedDelegate;            AppEnvironment.FontSmoothingChanged += fontSmoothingChangedDelegate;            AppEnvironment.TextAlignmentChanged += alignmentChangedDelegate;            AppEnvironment.AntiAliasingChanged += antiAliasChangedDelegate;            AppEnvironment.PrimaryColorChanged += foreColorChangedDelegate;            AppEnvironment.SecondaryColorChanged += new EventHandler(BackColorChangedHandler);            AppEnvironment.AlphaBlendingChanged += new EventHandler(AlphaBlendingChangedHandler);            this.threadPool = new PaintDotNet.Threading.ThreadPool();            this.moveNub = new MoveNubRenderer(this.RendererList);            this.moveNub.Shape = MoveNubShape.Compass;            this.moveNub.Size = new SizeF(10, 10);            this.moveNub.Visible = false;            this.RendererList.Add(this.moveNub, false);        }        protected override void OnDeactivate()        {            PdnBaseForm.UnregisterFormHotKey(Keys.Back, OnBackspaceTyped);            base.OnDeactivate();            switch (mode)            {                case EditingMode.Editing:                    SaveHistoryMemento();                    break;                case EditingMode.EmptyEdit:                    RedrawText(false);                    break;                case EditingMode.NotEditing:                    break;                default:                    throw new InvalidEnumArgumentException("Invalid Editing Mode");            }            if (ra != null)            {                ra.Dispose();                ra = null;            }            if (saved != null)            {                saved.Dispose();                saved = null;            }            AppEnvironment.BrushInfoChanged -= brushChangedDelegate;            AppEnvironment.FontInfoChanged -= fontChangedDelegate;            AppEnvironment.FontSmoothingChanged -= fontSmoothingChangedDelegate;            AppEnvironment.TextAlignmentChanged -= alignmentChangedDelegate;            AppEnvironment.AntiAliasingChanged -= antiAliasChangedDelegate;            AppEnvironment.PrimaryColorChanged -= foreColorChangedDelegate;            AppEnvironment.SecondaryColorChanged -= new EventHandler(BackColorChangedHandler);            AppEnvironment.AlphaBlendingChanged -= new EventHandler(AlphaBlendingChangedHandler);            StopEditing();            this.threadPool = null;            this.RendererList.Remove(this.moveNub);            this.moveNub.Dispose();            this.moveNub = null;            if (this.textToolCursor != null)            {                this.textToolCursor.Dispose();                this.textToolCursor = null;            }        }        private void StopEditing()        {            mode = EditingMode.NotEditing;            pulseEnabled = false;            lines = null;            this.moveNub.Visible = false;        }        private void StartEditing()        {            this.linePos = 0;            this.textPos = 0;            this.lines = new ArrayList();            this.sizes = null;            this.lines.Add(string.Empty);            this.startTime = DateTime.Now;            this.mode = EditingMode.EmptyEdit;            this.pulseEnabled = true;            UpdateStatusText();        }        private void UpdateStatusText()        {            string text;            ImageResource image;            if (this.tracking)            {                text = GetStatusBarXYText();                image = Image;            }            else            {                text = PdnResources.GetString("TextTool.StatusText.StartTyping");                image = null;            }            SetStatus(image, text);        }        private void PerformEnter()        {            if (this.lines == null)            {                return;            }            string currentLine = (string)this.lines[this.linePos];            if (this.textPos == currentLine.Length)            {                // If we are at the end of a line, insert an empty line at the next line                this.lines.Insert(this.linePos + 1, string.Empty);            }            else            {                this.lines.Insert(this.linePos + 1, currentLine.Substring(textPos, currentLine.Length - this.textPos));                this.lines[this.linePos] = ((string)this.lines[this.linePos]).Substring(0, this.textPos);            }            this.linePos++;            this.textPos = 0;            this.sizes = null;        }        private void PerformBackspace()        {            if (textPos == 0 && linePos > 0)            {                int ntp = ((string)lines[linePos - 1]).Length;                lines[linePos - 1] = ((string)lines[linePos - 1]) + ((string)lines[linePos]);                lines.RemoveAt(linePos);                linePos--;                textPos = ntp;                sizes = null;            }            else if (textPos > 0)            {                string ln = (string)lines[linePos];                // If we are at the end of a line, we don't need to place a compound string                if (textPos == ln.Length)                {                    lines[linePos] = ln.Substring(0, ln.Length - 1);                }                else                {                    lines[linePos] = ln.Substring(0, textPos - 1) + ln.Substring(textPos);                }                textPos--;                sizes = null;            }        }        private void PerformControlBackspace()        {            if (textPos == 0 && linePos > 0)            {                PerformBackspace();            }            else if (textPos > 0)            {                string currentLine = (string)lines[linePos];                int ntp = textPos;                if (Char.IsLetterOrDigit(currentLine[ntp - 1]))                {                    while (ntp > 0 && (Char.IsLetterOrDigit(currentLine[ntp - 1])))                    {                        ntp--;                    }                }                else if (Char.IsWhiteSpace(currentLine[ntp - 1]))                {                    while (ntp > 0 && (Char.IsWhiteSpace(currentLine[ntp - 1])))                    {                        ntp--;                    }                }                else if (Char.IsPunctuation(currentLine[ntp - 1]))                {                    while (ntp > 0 && (Char.IsPunctuation(currentLine[ntp - 1])))                    {                        ntp--;                    }                }                else                {                    ntp--;                }                lines[linePos] = currentLine.Substring(0, ntp) + currentLine.Substring(textPos);                textPos = ntp;                sizes = null;            }        }        private void PerformDelete()        {            // Where are we?!            if ((linePos == lines.Count - 1) && (textPos == ((string)lines[lines.Count - 1]).Length))            {                // If the cursor is at the end of the text block                return;            }            else if (textPos == ((string)lines[linePos]).Length)            {                // End of a line, must merge strings                lines[linePos] = ((string)lines[linePos]) + ((string)lines[linePos + 1]);                lines.RemoveAt(linePos + 1);            }            else            {                // Middle of a line somewhere                lines[linePos] = ((string)lines[linePos]).Substring(0, textPos) + ((string)lines[linePos]).Substring(textPos + 1);            }            // Check for state change            if (lines.Count == 1 && ((string)lines[0]) == "")            {                mode = EditingMode.EmptyEdit;            }            sizes = null;        }        private void PerformControlDelete()        {            // where are we?!            if ((linePos == lines.Count - 1) && (textPos == ((string)lines[lines.Count - 1]).Length))            {                // If the cursor is at the end of the text block                return;            }            else if (textPos == ((string)lines[linePos]).Length)            {                // End of a line, must merge strings                lines[linePos] = ((string)lines[linePos]) + ((string)lines[linePos + 1]);                lines.RemoveAt(linePos + 1);            }            else            {                // Middle of a line somewhere                int ntp = textPos;                string currentLine = (string)lines[linePos];                if (Char.IsLetterOrDigit(currentLine[ntp]))                {                    while (ntp < currentLine.Length && (Char.IsLetterOrDigit(currentLine[ntp])))                    {                        currentLine = currentLine.Remove(ntp, 1);                    }                }                else if (Char.IsWhiteSpace(currentLine[ntp]))                {                    while (ntp < currentLine.Length && (Char.IsWhiteSpace(currentLine[ntp])))                    {                        currentLine = currentLine.Remove(ntp, 1);                    }                }                else if (Char.IsPunctuation(currentLine[ntp]))                {                    while (ntp < currentLine.Length && (Char.IsPunctuation(currentLine[ntp])))                    {                        currentLine = currentLine.Remove(ntp, 1);                    }                }                else                {                    ntp--;                }                lines[linePos] = currentLine;            }            // Check for state change            if (lines.Count == 1 && ((string)lines[0]) == "")            {                mode = EditingMode.EmptyEdit;            }            sizes = null;        }        private void PerformLeft()        {            if (textPos > 0)            {                textPos--;            }            else if (textPos == 0 && linePos > 0)            {                linePos--;                textPos = ((string)lines[linePos]).Length;            }        }        private void PerformControlLeft()        {            if (textPos > 0)            {                int ntp = textPos;                string currentLine = (string)lines[linePos];                if (Char.IsLetterOrDigit(currentLine[ntp - 1]))                {                    while (ntp > 0 && (Char.IsLetterOrDigit(currentLine[ntp - 1])))                    {                        ntp--;                    }                }                else if (Char.IsWhiteSpace(currentLine[ntp - 1]))                {                    while (ntp > 0 && (Char.IsWhiteSpace(currentLine[ntp - 1])))                    {                        ntp--;                    }                }                else if (ntp > 0 && Char.IsPunctuation(currentLine[ntp - 1]))                {                    while (ntp > 0 && Char.IsPunctuation(currentLine[ntp - 1]))                    {                        ntp--;                    }                }                else                {                    ntp--;                }                textPos = ntp;            }            else if (textPos == 0 && linePos > 0)            {                linePos--;                textPos = ((string)lines[linePos]).Length;            }        }        private void PerformRight()        {            if (textPos < ((string)lines[linePos]).Length)            {                textPos++;            }            else if (textPos == ((string)lines[linePos]).Length && linePos < lines.Count - 1)            {                linePos++;                textPos = 0;            }        }        private void PerformControlRight()        {            if (textPos < ((string)lines[linePos]).Length)            {                int ntp = textPos;                string currentLine = (string)lines[linePos];                if (Char.IsLetterOrDigit(currentLine[ntp]))                {                    while (ntp < currentLine.Length && (Char.IsLetterOrDigit(currentLine[ntp])))                    {                        ntp++;                    }                }                else if (Char.IsWhiteSpace(currentLine[ntp]))                {                    while (ntp < currentLine.Length && (Char.IsWhiteSpace(currentLine[ntp])))                    {                        ntp++;                    }                }                else if (ntp > 0 && Char.IsPunctuation(currentLine[ntp]))                {                    while (ntp < currentLine.Length && Char.IsPunctuation(currentLine[ntp]))                    {                        ntp++;                    }                }                else                {                    ntp++;                }                textPos = ntp;            }            else if (textPos == ((string)lines[linePos]).Length && linePos < lines.Count - 1)            {                linePos++;                textPos = 0;            }        }        private void PerformUp()        {            PointF p = TextPositionToPoint(new Position(linePos, textPos));            p.Y -= this.sizes[0].Height; //font.Height;            Position np = PointToTextPosition(p);            linePos = np.Line;            textPos = np.Offset;        }        private void PerformDown()        {            if (linePos == lines.Count - 1)            {                // last line -> don't do squat            }            else            {                PointF p = TextPositionToPoint(new Position(linePos, textPos));                p.Y += this.sizes[0].Height; //font.Height;                Position np = PointToTextPosition(p);                linePos = np.Line;                textPos = np.Offset;            }        }        private Point GetUpperLeft(Size sz, int line)        {            Point p = clickPoint;            p.Y = (int)(p.Y - (0.5 * sz.Height) + (line * sz.Height));            switch (alignment)            {                case TextAlignment.Center:                    p.X = (int)(p.X - (0.5) * sz.Width);                    break;                case TextAlignment.Right:                    p.X = (int)(p.X - sz.Width);                    break;            }            return p;        }        private Size StringSize(string s)        {            // We measure using a 1x1 device context to avoid performance problems that arise otherwise with large images.            using (Surface window = ScratchSurface.CreateWindow(new Rectangle(0, 0, 1, 1)))            {                using (RenderArgs ra2 = new RenderArgs(window))                {                    return SystemLayer.Fonts.MeasureString(                        ra2.Graphics,                        this.font,                        s,                        AppEnvironment.AntiAliasing(),                        AppEnvironment.FontSmoothing());                }            }        }        private sealed class Position        {            private int line;            public int Line            {                get                {                    return line;                }                set                {                    if (value >= 0)                    {                        line = value;                    }                    else                    {                        line = 0;                    }                }            }            private int offset;            public int Offset            {                get                {                    return offset;                }                set                {                    if (value >= 0)                    {                        offset = value;                    }                    else                    {                        offset = 0;                    }                }            }            public Position(int line, int offset)            {                this.line = line;                this.offset = offset;            }        }        private void SaveHistoryMemento()        {            pulseEnabled = false;            RedrawText(false);            if (saved != null)            {                PdnRegion hitTest = Selection.CreateRegion();                hitTest.Intersect(saved.Region);                if (!hitTest.IsEmpty())                {                    BitmapHistoryMemento bha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace,                        ActiveLayerIndex, saved);                    if (this.currentHA == null)                    {                        //HistoryStack.PushNewMemento(bha);                    }                    else                    {                        this.currentHA.PushNewAction(bha);                        this.currentHA = null;                    }                }                hitTest.Dispose();                saved.Dispose();                saved = null;            }        }        private void DrawText(Surface dst, Font textFont, string text, Point pt, Size measuredSize, bool antiAliasing, Brush brush)        {            Rectangle dstRect = new Rectangle(pt, measuredSize);            Rectangle dstRectClipped = Rectangle.Intersect(dstRect, ScratchSurface.Bounds);            if (dstRectClipped.Width == 0 || dstRectClipped.Height == 0)            {                return;            }            using (Surface surface = new Surface(8, 8))            {                using (RenderArgs renderArgs = new RenderArgs(surface))                {                    renderArgs.Graphics.FillRectangle(brush, 0, 0, surface.Width, surface.Height);                }                DrawText(dst, textFont, text, pt, measuredSize, antiAliasing, surface);            }        }        private unsafe void DrawText(Surface dst, Font textFont, string text, Point pt, Size measuredSize, bool antiAliasing, Surface brush8x8)        {            Point pt2 = pt;            Size measuredSize2 = measuredSize;            int offset = (int)textFont.Height;            pt.X -= offset;            measuredSize.Width += 2 * offset;            Rectangle dstRect = new Rectangle(pt, measuredSize);            Rectangle dstRectClipped = Rectangle.Intersect(dstRect, ScratchSurface.Bounds);            if (dstRectClipped.Width == 0 || dstRectClipped.Height == 0)            {                return;            }            // We only use the first 8,8 of brush            using (RenderArgs renderArgs = new RenderArgs(this.ScratchSurface))            {                renderArgs.Graphics.FillRectangle(Brushes.White, pt.X, pt.Y, measuredSize.Width, measuredSize.Height);                if (measuredSize.Width > 0 && measuredSize.Height > 0)                {                    using (Surface s2 = renderArgs.Surface.CreateWindow(dstRectClipped))                    {                        using (RenderArgs renderArgs2 = new RenderArgs(s2))                        {                            SystemLayer.Fonts.DrawText(                                renderArgs2.Graphics,                                this.font,                                text,                                new Point(dstRect.X - dstRectClipped.X + offset, dstRect.Y - dstRectClipped.Y),                                AppEnvironment.AntiAliasing(),                                AppEnvironment.FontSmoothing());                        }                    }                }                // Mask out anything that isn't within the user's clip region (selected region)                using (PdnRegion clip = Selection.CreateRegion())                {                    clip.Xor(renderArgs.Surface.Bounds); // invert                    clip.Intersect(new Rectangle(pt, measuredSize));                    renderArgs.Graphics.FillRegion(Brushes.White, clip.GetRegionReadOnly());                }                int skipX;                if (pt.X < 0)                {                    skipX = -pt.X;                }                else                {                    skipX = 0;                }                int xEnd = Math.Min(dst.Width, pt.X + measuredSize.Width);                bool blending = AppEnvironment.AlphaBlending();                if (dst.IsColumnVisible(pt.X + skipX))                {                    for (int y = pt.Y; y < pt.Y + measuredSize.Height; ++y)                    {                        if (!dst.IsRowVisible(y))                        {                            continue;                        }                        ColorBgra* dstPtr = dst.GetPointAddressUnchecked(pt.X + skipX, y);                        ColorBgra* srcPtr = ScratchSurface.GetPointAddress(pt.X + skipX, y);                        ColorBgra* brushPtr = brush8x8.GetRowAddressUnchecked(y & 7);                        for (int x = pt.X + skipX; x < xEnd; ++x)                        {                            ColorBgra srcPixel = *srcPtr;                            ColorBgra dstPixel = *dstPtr;                            ColorBgra brushPixel = brushPtr[x & 7];                            int alpha = ((255 - srcPixel.R) * brushPixel.A) / 255; // we could use srcPixel.R, .G, or .B -- the choice here is arbitrary                            brushPixel.A = (byte)alpha;                            if (srcPtr->R == 255) // could use R, G, or B -- arbitrary choice                            {                                // do nothing -- leave dst alone                            }                            else if (alpha == 255 || !blending)                            {                                // copy it straight over                                *dstPtr = brushPixel;                            }                            else                            {                                // do expensive blending                                *dstPtr = UserBlendOps.NormalBlendOp.ApplyStatic(dstPixel, brushPixel);                            }                            ++dstPtr;                            ++srcPtr;                        }                    }                }            }        }        /// <summary>        /// Redraws the Text on the screen        /// </summary>        /// <remarks>        /// assumes that the <b>font</b> and the <b>alignment</b> are already set        /// </remarks>        /// <param name="cursorOn"></param>        private void RedrawText(bool cursorOn)        {            if (this.ignoreRedraw > 0)            {                return;            }            if (saved != null)            {                saved.Draw(ra.Surface);                ActiveLayer.Invalidate(saved.Region);                saved.Dispose();                saved = null;            }            // Save the Space behind the lines            Rectangle[] rects = new Rectangle[lines.Count + 1];            Point[] localUls = new Point[lines.Count];            // All Lines            bool recalcSizes = false;            if (this.sizes == null)            {                recalcSizes = true;                this.sizes = new Size[lines.Count + 1];            }            if (recalcSizes)            {                for (int i = 0; i < lines.Count; ++i)                {                    this.threadPool.QueueUserWorkItem(new System.Threading.WaitCallback(this.MeasureText),                        BoxedConstants.GetInt32(i));                }                this.threadPool.Drain();            }            for (int i = 0; i < lines.Count; ++i)            {                Point upperLeft = GetUpperLeft(sizes[i], i);                localUls[i] = upperLeft;                Rectangle rect = new Rectangle(upperLeft, sizes[i]);                rects[i] = rect;            }            // The Cursor Line            string cursorLine = ((string)lines[linePos]).Substring(0, textPos);            Size cursorLineSize;            Point cursorUL;            Rectangle cursorRect;            bool emptyCursorLineFlag;            if (cursorLine.Length == 0)            {                emptyCursorLineFlag = true;                Size fullLineSize = sizes[linePos];                cursorLineSize = new Size(2, (int)(Math.Ceiling(font.GetHeight())));                cursorUL = GetUpperLeft(fullLineSize, linePos);                cursorRect = new Rectangle(cursorUL, cursorLineSize);            }            else if (cursorLine.Length == ((string)lines[linePos]).Length)            {                emptyCursorLineFlag = false;                cursorLineSize = sizes[linePos];                cursorUL = localUls[linePos];                cursorRect = new Rectangle(cursorUL, cursorLineSize);            }            else            {                emptyCursorLineFlag = false;                cursorLineSize = StringSize(cursorLine);                cursorUL = localUls[linePos];                cursorRect = new Rectangle(cursorUL, cursorLineSize);            }            rects[lines.Count] = cursorRect;            // Account for overhang on italic or fancy fonts            int offset = (int)this.font.Height;            for (int i = 0; i < rects.Length; ++i)            {                rects[i].X -= offset;                rects[i].Width += 2 * offset;            }            // Set the saved region            using (PdnRegion reg = Utility.RectanglesToRegion(Utility.InflateRectangles(rects, 3)))            {                saved = new IrregularSurface(ra.Surface, reg);            }            // Draw the Lines            this.uls = localUls;            for (int i = 0; i < lines.Count; i++)            {                threadPool.QueueUserWorkItem(new System.Threading.WaitCallback(this.RenderText), BoxedConstants.GetInt32(i));            }            threadPool.Drain();            // Draw the Cursor            if (cursorOn)            {                using (Pen cursorPen = new Pen(Color.FromArgb(255, AppEnvironment.PrimaryColor().ToColor()), 2))                {                    if (emptyCursorLineFlag)                    {                        ra.Graphics.FillRectangle(cursorPen.Brush, cursorRect);                    }                    else                    {                        ra.Graphics.DrawLine(cursorPen, new Point(cursorRect.Right, cursorRect.Top), new Point(cursorRect.Right, cursorRect.Bottom));                    }                }            }            PlaceMoveNub();            UpdateStatusText();            ActiveLayer.Invalidate(saved.Region);            Update();        }        private string GetStatusBarXYText()        {            string unitsAbbreviationXY;            string xString;            string yString;            Document.CoordinatesToStrings(AppWorkspace.GetUnits(), this.uls[0].X, this.uls[0].Y, out xString, out yString, out unitsAbbreviationXY);            string statusBarText = string.Format(                this.statusBarTextFormat,                xString,                unitsAbbreviationXY,                yString,                unitsAbbreviationXY);            return statusBarText;        }        // Only used when measuring via background threads        private void MeasureText(object lineNumberObj)        {            int lineNumber = (int)lineNumberObj;            this.sizes[lineNumber] = StringSize((string)lines[lineNumber]);        }        // Only used when rendering via background threads        private Point[] uls;        private Size[] sizes;        private void RenderText(object lineNumberObj)        {            int lineNumber = (int)lineNumberObj;            using (Brush brush = AppEnvironment.CreateBrush(false))            {                DrawText(ra.Surface, this.font, (string)this.lines[lineNumber], this.uls[lineNumber], this.sizes[lineNumber], AppEnvironment.AntiAliasing(), brush);            }        }        private void PlaceMoveNub()        {            if (this.uls != null && this.uls.Length > 0)            {                Point pt = this.uls[uls.Length - 1];                pt.X += this.sizes[uls.Length - 1].Width;                pt.Y += this.sizes[uls.Length - 1].Height;                pt.X += (int)(10.0 / DocumentWorkspace.GetRatio());                pt.Y += (int)(10.0 / DocumentWorkspace.GetRatio());                pt.X = (int)Math.Round(Math.Min(this.ra.Surface.Width - this.moveNub.Size.Width, pt.X));                pt.X = (int)Math.Round(Math.Max(this.moveNub.Size.Width, pt.X));                pt.Y = (int)Math.Round(Math.Min(this.ra.Surface.Height - this.moveNub.Size.Height, pt.Y));                pt.Y = (int)Math.Round(Math.Max(this.moveNub.Size.Height, pt.Y));                this.moveNub.Location = pt;            }        }        protected override void OnKeyDown(KeyEventArgs e)        {            switch (e.KeyCode)            {                case Keys.Space:                    if (mode != EditingMode.NotEditing)                    {                        // Prevent pan cursor from flicking to 'hand w/ the X' whenever use types a space in their text                        e.Handled = true;                    }                    break;                case Keys.ControlKey:                    if (!this.controlKeyDown)                    {                        this.controlKeyDown = true;                        this.controlKeyDownTime = DateTime.Now;                    }                    break;                // Make sure these are not used to scroll the document around                case Keys.Home | Keys.Shift:                case Keys.Home:                case Keys.End:                case Keys.End | Keys.Shift:                case Keys.Next | Keys.Shift:                case Keys.Next:                case Keys.Prior | Keys.Shift:                case Keys.Prior:                    if (this.mode != EditingMode.NotEditing)                    {                        OnKeyPress(e.KeyCode);                        e.Handled = true;                    }                    break;                case Keys.Tab:                    if ((e.Modifiers & Keys.Control) == 0)                    {                        if (this.mode != EditingMode.NotEditing)                        {                            OnKeyPress(e.KeyCode);                            e.Handled = true;                        }                    }                    break;                case Keys.Back:                case Keys.Delete:                    if (this.mode != EditingMode.NotEditing)                    {                        OnKeyPress(e.KeyCode);                        e.Handled = true;                    }                    break;            }            // Ensure text is on screen when they are typing            if (this.mode != EditingMode.NotEditing)            {                Point p = Point.Truncate(TextPositionToPoint(new Position(linePos, textPos)));                Rectangle bounds = Utility.RoundRectangle(DocumentWorkspace.GetVisibleDocumentRectangleF());                bounds.Inflate(-(int)font.Height, -(int)font.Height);                if (!bounds.Contains(p))                {                    PointF newCenterPt = Utility.GetRectangleCenter((RectangleF)bounds);                    // horizontally off                    if (p.X > bounds.Right || p.Y < bounds.Left)                    {                        newCenterPt.X = p.X;                    }                    // vertically off                    if (p.Y > bounds.Bottom || p.Y < bounds.Top)                    {                        newCenterPt.Y = p.Y;                    }                    DocumentWorkspace.SetDocumentCenterPointF(newCenterPt);                }            }            base.OnKeyDown(e);        }        protected override void OnKeyUp(KeyEventArgs e)        {            switch (e.KeyCode)            {                case Keys.ControlKey:                    TimeSpan heldDuration = (DateTime.Now - this.controlKeyDownTime);                    // If the user taps Ctrl, then we should toggle the visiblity of the moveNub                    if (heldDuration < this.controlKeyDownThreshold)                    {                        this.enableNub = !this.enableNub;                    }                    this.controlKeyDown = false;                    break;            }            base.OnKeyUp(e);        }        protected override void OnKeyPress(KeyPressEventArgs e)        {            switch (e.KeyChar)            {                case (char)13: // Enter                    if (tracking)                    {                        e.Handled = true;                    }                    break;                case (char)27: // Escape                    if (tracking)                    {                        e.Handled = true;                    }                    else                    {                        if (mode == EditingMode.Editing)                        {                            SaveHistoryMemento();                        }                        else if (mode == EditingMode.EmptyEdit)                        {                            RedrawText(false);                        }                        if (mode != EditingMode.NotEditing)                        {                            e.Handled = true;                            StopEditing();                        }                    }                    break;            }            if (!e.Handled && mode != EditingMode.NotEditing && !tracking)            {                e.Handled = true;                if (mode == EditingMode.EmptyEdit)                {                    mode = EditingMode.Editing;                    CompoundHistoryMemento cha = new CompoundHistoryMemento(Name, Image, new List<HistoryMemento>());                    this.currentHA = cha;                    //HistoryStack.PushNewMemento(cha);                }                if (!char.IsControl(e.KeyChar))                {                    InsertCharIntoString(e.KeyChar);                    textPos++;                    RedrawText(true);                }            }            base.OnKeyPress(e);        }        protected override void OnKeyPress(Keys keyData)        {            bool keyHandled = true;            Keys key = keyData & Keys.KeyCode;            Keys modifier = keyData & Keys.Modifiers;            if (tracking)            {                keyHandled = false;            }            else if (modifier == Keys.Alt)            {                // ignore so they can use Alt+#### to type special characters            }            else if (mode != EditingMode.NotEditing)            {                switch (key)                {                    case Keys.Back:                        if (modifier == Keys.Control)                        {                            PerformControlBackspace();                        }                        else                        {                            PerformBackspace();                        }                        break;                    case Keys.Delete:                        if (modifier == Keys.Control)                        {                            PerformControlDelete();                        }                        else                        {                            PerformDelete();                        }                        break;                    case Keys.Enter:                        PerformEnter();                        break;                    case Keys.Left:                        if (modifier == Keys.Control)                        {                            PerformControlLeft();                        }                        else                        {                            PerformLeft();                        }                        break;                    case Keys.Right:                        if (modifier == Keys.Control)                        {                            PerformControlRight();                        }                        else                        {                            PerformRight();                        }                        break;                    case Keys.Up:                        PerformUp();                        break;                    case Keys.Down:                        PerformDown();                        break;                    case Keys.Home:                        if (modifier == Keys.Control)                        {                            linePos = 0;                        }                        textPos = 0;                        break;                    case Keys.End:                        if (modifier == Keys.Control)                        {                            linePos = lines.Count - 1;                        }                        textPos = ((string)lines[linePos]).Length;                        break;                    default:                        keyHandled = false;                        break;                }                this.startTime = DateTime.Now;                if (this.mode != EditingMode.NotEditing && keyHandled)                {                    RedrawText(true);                }            }            if (!keyHandled)            {                base.OnKeyPress(keyData);            }        }        private PointF TextPositionToPoint(Position p)        {            PointF pf = new PointF(0, 0);            Size sz = StringSize(((string)lines[p.Line]).Substring(0, p.Offset));            Size fullSz = StringSize((string)lines[p.Line]);            switch (alignment)            {                case TextAlignment.Left:                    pf = new PointF(clickPoint.X + sz.Width, clickPoint.Y + (sz.Height * p.Line));                    break;                case TextAlignment.Center:                    pf = new PointF(clickPoint.X + (sz.Width - (fullSz.Width / 2)), clickPoint.Y + (sz.Height * p.Line));                    break;                case TextAlignment.Right:                    pf = new PointF(clickPoint.X + (sz.Width - fullSz.Width), clickPoint.Y + (sz.Height * p.Line));                    break;                default:                    throw new InvalidEnumArgumentException("Invalid Alignment");            }            return pf;        }        private int FindOffsetPosition(float offset, string line, int lno)        {            for (int i = 0; i < line.Length; i++)            {                PointF pf = TextPositionToPoint(new Position(lno, i));                float dx = pf.X - clickPoint.X;                if (dx >= offset)                {                    return i;                }            }            return line.Length;        }        private Position PointToTextPosition(PointF pf)        {            float dx = pf.X - clickPoint.X;            float dy = pf.Y - clickPoint.Y;            int line = (int)Math.Floor(dy / (float)this.sizes[0].Height);            if (line < 0)            {                line = 0;            }            else if (line >= lines.Count)            {                line = lines.Count - 1;            }            int offset = FindOffsetPosition(dx, (string)lines[line], line);            Position p = new Position(line, offset);            if (p.Offset >= ((string)lines[p.Line]).Length)            {                p.Offset = ((string)lines[p.Line]).Length;            }            return p;        }        protected override void OnMouseMove(MouseEventArgs e)        {            if (tracking)            {                Point newMouseXY = new Point(e.X, e.Y);                Size delta = new Size(newMouseXY.X - startMouseXY.X, newMouseXY.Y - startMouseXY.Y);                this.clickPoint = new Point(this.startClickPoint.X + delta.Width, this.startClickPoint.Y + delta.Height);                RedrawText(false);                UpdateStatusText();            }            else            {                bool touchingNub = this.moveNub.IsPointTouching(new Point(e.X, e.Y), false);                if (touchingNub && this.moveNub.Visible)                {                    this.Cursor = this.handCursor;                }                else                {                    this.Cursor = this.textToolCursor;                }            }            base.OnMouseMove(e);        }        protected override void OnMouseUp(MouseEventArgs e)        {            if (tracking)            {                OnMouseMove(e);                tracking = false;                UpdateStatusText();            }            base.OnMouseUp(e);        }        protected override void OnMouseDown(MouseEventArgs e)        {            base.OnMouseDown(e);            bool touchingMoveNub = this.moveNub.IsPointTouching(new Point(e.X, e.Y), false);            if (this.mode != EditingMode.NotEditing && (e.Button == MouseButtons.Right || touchingMoveNub))            {                this.tracking = true;                this.startMouseXY = new Point(e.X, e.Y);                this.startClickPoint = this.clickPoint;                this.Cursor = this.handCursorMouseDown;                UpdateStatusText();            }            else if (e.Button == MouseButtons.Left)            {                if (saved != null)                {                    Rectangle bounds = Utility.GetRegionBounds(saved.Region);                    bounds.Inflate(font.Height, font.Height);                    if (lines != null && bounds.Contains(e.X, e.Y))                    {                        Position p = PointToTextPosition(new PointF(e.X, e.Y + (font.Height / 2)));                        linePos = p.Line;                        textPos = p.Offset;                        RedrawText(true);                        return;                    }                }                switch (mode)                {                    case EditingMode.Editing:                        SaveHistoryMemento();                        StopEditing();                        break;                    case EditingMode.EmptyEdit:                        RedrawText(false);                        StopEditing();                        break;                }                clickPoint = new Point(e.X, e.Y);                StartEditing();                RedrawText(true);            }        }        protected override void OnPulse()        {            base.OnPulse();            if (!pulseEnabled)            {                return;            }            TimeSpan ts = (DateTime.Now - startTime);            long ms = Utility.TicksToMs(ts.Ticks);            bool pulseCursorState;            if (0 == ((ms / cursorInterval) % 2))            {                pulseCursorState = true;            }            else            {                pulseCursorState = false;            }            pulseCursorState &= this.Focused;            if (IsFormActive)            {                pulseCursorState &= ((ModifierKeys & Keys.Control) == 0);            }            if (pulseCursorState != lastPulseCursorState)            {                RedrawText(pulseCursorState);                lastPulseCursorState = pulseCursorState;            }            if (IsFormActive && (ModifierKeys & Keys.Control) != 0)            {                // hide the nub while Ctrl is held down                this.moveNub.Visible = false;            }            else            {                this.moveNub.Visible = true;            }            // don't show the nub while the user is moving the text around            this.moveNub.Visible &= !tracking;            // don't show the nub when the user has tapped Ctrl            this.moveNub.Visible &= this.enableNub;            // 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 second            long tick = ts.Ticks % period;            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]            if (this.moveNub != null)            {                int newAlpha = (int)(sin * 255.0);                int clampedAlpha = Utility.Clamp(newAlpha, 0, 255);                this.moveNub.Alpha = clampedAlpha;            }            PlaceMoveNub();        }        protected override void OnPasteQuery(IDataObject data, out bool canHandle)        {            base.OnPasteQuery(data, out canHandle);            if (data.GetDataPresent(DataFormats.StringFormat, true) &&                this.Active &&                this.mode != EditingMode.NotEditing)            {                canHandle = true;            }        }        protected override void OnPaste(IDataObject data, out bool handled)        {            base.OnPaste(data, out handled);            if (data.GetDataPresent(DataFormats.StringFormat, true) &&                this.Active &&                this.mode != EditingMode.NotEditing)            {                ++this.ignoreRedraw;                string text = (string)data.GetData(DataFormats.StringFormat, true);                foreach (char c in text)                {                    if (c == '\n')                    {                        this.PerformEnter();                    }                    else                    {                        this.PerformKeyPress(new KeyPressEventArgs(c));                    }                }                handled = true;                --this.ignoreRedraw;                this.moveNub.Visible = true;                this.RedrawText(false);            }        }        private void InsertCharIntoString(char c)        {            lines[linePos] = ((string)lines[linePos]).Insert(textPos, c.ToString());            this.sizes = null;        }        public TextTool(IDocumentWorkspace documentWorkspace)            : base(documentWorkspace,                   PdnResources.GetImageResource("Icons.TextToolIcon.png"),                   PdnResources.GetString("TextTool.Name"),                   PdnResources.GetString("TextTool.HelpText"),                   't',                   false,                   ToolBarConfigItems.Brush | ToolBarConfigItems.Text | ToolBarConfigItems.AlphaBlending | ToolBarConfigItems.Antialiasing)        {        }    }}
 |