TextTool.cs :  » GUI » Paint.net » PaintDotNet » Tools » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » GUI » Paint.net 
Paint.net » PaintDotNet » Tools » TextTool.cs
/////////////////////////////////////////////////////////////////////////////////
// Paint.NET                                                                   //
// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors.     //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.          //
// See src/Resources/Files/License.txt for full licensing and attribution      //
// details.                                                                    //
// .                                                                           //
/////////////////////////////////////////////////////////////////////////////////

using PaintDotNet;
using PaintDotNet.HistoryMementos;
using PaintDotNet.SystemLayer;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;

namespace PaintDotNet.Tools{
    internal 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.Units, 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.ScaleFactor.Ratio);
                pt.Y += (int)(10.0 / DocumentWorkspace.ScaleFactor.Ratio);

                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.VisibleDocumentRectangleF);
                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.DocumentCenterPointF = 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(DocumentWorkspace 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)
        {
        }
    }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.