/src/tools/TextTool.cs
C# | 1599 lines | 1297 code | 234 blank | 68 comment | 282 complexity | fa81dd48fc8b95a75820e4dc60c62374 MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // 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.Base;
- using PaintDotNet.HistoryMementos;
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Windows.Forms;
- using System.Collections;
- using System.ComponentModel;
-
- namespace PaintDotNet.Tools
- {
- internal class TextTool
- : Tool
- {
- private enum EditingMode
- {
- NotEditing,
- EmptyEdit,
- Editing
- }
-
- private readonly 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 DateTime _startTime;
- private bool _lastPulseCursorState;
- private Cursor _textToolCursor;
- private Threading.ThreadPool _threadPool;
- private bool _enableNub = true;
-
- private CompoundHistoryMemento _currentHa;
-
- private bool _controlKeyDown;
- 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) return;
- _sizes = null;
- RedrawText(true);
- }
-
- private EventHandler _fontSmoothingChangedDelegate;
- private void FontSmoothingChangedHandler(object sender, EventArgs e)
- {
- if (_mode == EditingMode.NotEditing) return;
- _sizes = null;
- RedrawText(true);
- }
-
- private EventHandler _alignmentChangedDelegate;
- private void AlignmentChangedHandler(object sender, EventArgs a)
- {
- _alignment = AppEnvironment.TextAlignment;
- if (_mode == EditingMode.NotEditing) return;
- _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) return;
- _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 (!DocumentWorkspace.Visible)
- {
- return false;
- }
- if (_mode != EditingMode.NotEditing)
- {
- OnKeyPress(Keys.Back);
- return true;
- }
- return false;
- }
-
- protected override void OnActivate()
- {
- PdnBaseForm.RegisterFormHotKey(Keys.Back, OnBackspaceTyped);
-
- base.OnActivate();
-
- _textToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.TextToolCursor.cur"));
- Cursor = _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 += BackColorChangedHandler;
- AppEnvironment.AlphaBlendingChanged += AlphaBlendingChangedHandler;
-
- _threadPool = new Threading.ThreadPool();
-
- _moveNub = new MoveNubRenderer(RendererList)
- {
- Shape = MoveNubShape.Compass,
- Size = new SizeF(10, 10),
- Visible = false
- };
- RendererList.Add(_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 -= BackColorChangedHandler;
- AppEnvironment.AlphaBlendingChanged -= AlphaBlendingChangedHandler;
-
- StopEditing();
- _threadPool = null;
-
- RendererList.Remove(_moveNub);
- _moveNub.Dispose();
- _moveNub = null;
-
- if (_textToolCursor == null) return;
- _textToolCursor.Dispose();
- _textToolCursor = null;
- }
-
- private void StopEditing()
- {
- _mode = EditingMode.NotEditing;
- _pulseEnabled = false;
- _lines = null;
- _moveNub.Visible = false;
- }
-
- private void StartEditing()
- {
- _linePos = 0;
- _textPos = 0;
- _lines = new ArrayList();
- _sizes = null;
- _lines.Add(string.Empty);
- _startTime = DateTime.Now;
- _mode = EditingMode.EmptyEdit;
- _pulseEnabled = true;
- UpdateStatusText();
- }
-
- private void UpdateStatusText()
- {
- string text;
- ImageResource image;
-
- if (_tracking)
- {
- text = GetStatusBarXyText();
- image = Image;
- }
- else
- {
- text = PdnResources.GetString("TextTool.StatusText.StartTyping");
- image = null;
- }
-
- SetStatus(image, text);
- }
-
- private void PerformEnter()
- {
- if (_lines == null)
- {
- return;
- }
-
- var currentLine = (string)_lines[_linePos]; //Change to string if var fails
-
- if (_textPos == currentLine.Length)
- {
- // If we are at the end of a line, insert an empty line at the next line
- _lines.Insert(_linePos + 1, string.Empty);
- }
- else
- {
- _lines.Insert(_linePos + 1, currentLine.Substring(_textPos, currentLine.Length - _textPos));
- _lines[_linePos] = ((string)_lines[_linePos]).Substring(0, _textPos);
- }
-
- _linePos++;
- _textPos = 0;
- _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)
- {
- var ln = (string)_lines[_linePos]; //Flip back to string if var fails
-
- // 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)
- {
- var currentLine = (string)_lines[_linePos]; //Flip back to string if var fails
- 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;
- }
- 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;
- }
- 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;
- var currentLine = (string)_lines[_linePos]; //Flip back to string if var fails
-
- 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;
- var currentLine = (string)_lines[_linePos]; //Flip to string if var fails
-
- 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;
- var currentLine = (string)_lines[_linePos]; //Flip to string if var fails
-
- 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 -= _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 += _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 = 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 (var ra2 = new RenderArgs(window)) //Flip back to RendeArgs if var fails
- {
- return SystemLayer.Fonts.MeasureString(
- ra2.Graphics,
- _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;*/
- //NEW CODE IS THE SINGLE LINE BELOW, ABOVE CODE IS KEPT IN CASE BUGS APPEAR USING THE BOTTOM ONE.
-
-
- line = value >= 0 ? value : 0;
- }
- }
-
- private int _offset;
- public int Offset
- {
- get
- {
- return _offset;
- }
-
- set {
- _offset = value >= 0 ? value : 0;
- }
- }
-
- public Position(int line, int offset)
- {
- this.line = line;
- _offset = offset;
- }
- }
-
- private void SaveHistoryMemento()
- {
- _pulseEnabled = false;
- RedrawText(false);
-
- if (_saved == null) return;
- PdnRegion hitTest = Selection.CreateRegion();
- hitTest.Intersect(_saved.Region);
-
- if (!hitTest.IsEmpty())
- {
- var bha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, //Change to BitmapHistoryMemento if var fails
- ActiveLayerIndex, _saved);
-
- if (_currentHa == null)
- {
- HistoryStack.PushNewMemento(bha);
- }
- else
- {
- _currentHa.PushNewAction(bha);
- _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)
- {
- var dstRect = new Rectangle(pt, measuredSize); //Change to Rectangle if var fails
- Rectangle dstRectClipped = Rectangle.Intersect(dstRect, ScratchSurface.Bounds);
-
- if (dstRectClipped.Width == 0 || dstRectClipped.Height == 0)
- {
- return;
- }
-
- using (var surface = new Surface(8, 8)) //Change to Surface if var fails
- {
- using (var renderArgs = new RenderArgs(surface)) //Change to RenderArgs if var fails
- {
- renderArgs.Graphics.FillRectangle(brush, 0, 0, surface.Width, surface.Height);
- }
-
- DrawText(dst, textFont, text, pt, measuredSize, surface);
- }
- }
-
- private unsafe void DrawText(Surface dst, Font textFont, string text, Point pt, Size measuredSize, Surface brush8X8)
- {
- Point pt2 = pt;
- Size measuredSize2 = measuredSize;
- var offset = textFont.Height; //Change to int if var fails
- pt.X -= offset;
- measuredSize.Width += 2 * offset;
- var dstRect = new Rectangle(pt, measuredSize); //Change to Rectangle if var fails
- 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 (var renderArgs = new RenderArgs(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 (var renderArgs2 = new RenderArgs(s2))
- {
- SystemLayer.Fonts.DrawText(
- renderArgs2.Graphics,
- _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)) return;
- 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 (_ignoreRedraw > 0)
- {
- return;
- }
-
- if (_saved != null)
- {
- _saved.Draw(_ra.Surface);
- ActiveLayer.Invalidate(_saved.Region);
- _saved.Dispose();
- _saved = null;
- }
-
- // Save the Space behind the lines
- var rects = new Rectangle[_lines.Count + 1];
- var localUls = new Point[_lines.Count];
-
- // All Lines
- bool recalcSizes = false;
-
- if (_sizes == null)
- {
- recalcSizes = true;
- _sizes = new Size[_lines.Count + 1];
- }
-
- if (recalcSizes)
- {
- for (int i = 0; i < _lines.Count; ++i)
- {
- _threadPool.QueueUserWorkItem(MeasureText,
- BoxedConstants.GetInt32(i));
- }
-
- _threadPool.Drain();
- }
-
- for (int i = 0; i < _lines.Count; ++i)
- {
- Point upperLeft = GetUpperLeft(_sizes[i], i);
- localUls[i] = upperLeft;
- var 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
- var offset = _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
- _uls = localUls;
-
- for (int i = 0; i < _lines.Count; i++)
- {
- _threadPool.QueueUserWorkItem(RenderText, BoxedConstants.GetInt32(i));
- }
-
- _threadPool.Drain();
-
- // Draw the Cursor
- if (cursorOn)
- {
- using (var 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, _uls[0].X, _uls[0].Y, out xString, out yString, out unitsAbbreviationXy);
-
- string statusBarText = string.Format(
- _statusBarTextFormat,
- xString,
- unitsAbbreviationXy,
- yString,
- unitsAbbreviationXy);
-
- return statusBarText;
- }
-
- // Only used when measuring via background threads
- private void MeasureText(object lineNumberObj)
- {
- var lineNumber = (int)lineNumberObj;
- _sizes[lineNumber] = StringSize((string)_lines[lineNumber]);
- }
-
- // Only used when rendering via background threads
- private Point[] _uls;
- private Size[] _sizes;
-
- private void RenderText(object lineNumberObj)
- {
- var lineNumber = (int)lineNumberObj;
-
- using (Brush brush = AppEnvironment.CreateBrush(false))
- {
- DrawText(_ra.Surface, _font, (string)_lines[lineNumber], _uls[lineNumber], _sizes[lineNumber], AppEnvironment.AntiAliasing, brush);
- }
- }
-
- private void PlaceMoveNub()
- {
- if (_uls == null || _uls.Length <= 0) return;
- Point pt = _uls[_uls.Length - 1];
- pt.X += _sizes[_uls.Length - 1].Width;
- pt.Y += _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(_ra.Surface.Width - _moveNub.Size.Width, pt.X));
- pt.X = (int)Math.Round(Math.Max(_moveNub.Size.Width, pt.X));
- pt.Y = (int)Math.Round(Math.Min(_ra.Surface.Height - _moveNub.Size.Height, pt.Y));
- pt.Y = (int)Math.Round(Math.Max(_moveNub.Size.Height, pt.Y));
-
- _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 (!_controlKeyDown)
- {
- _controlKeyDown = true;
- _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 (_mode != EditingMode.NotEditing)
- {
- OnKeyPress(e.KeyCode);
- e.Handled = true;
- }
- break;
-
- case Keys.Tab:
- if ((e.Modifiers & Keys.Control) == 0)
- {
- if (_mode != EditingMode.NotEditing)
- {
- OnKeyPress(e.KeyCode);
- e.Handled = true;
- }
- }
- break;
-
- case Keys.Back:
- case Keys.Delete:
- if (_mode != EditingMode.NotEditing)
- {
- OnKeyPress(e.KeyCode);
- e.Handled = true;
- }
- break;
- }
-
- // Ensure text is on screen when they are typing
- if (_mode != EditingMode.NotEditing)
- {
- Point p = Point.Truncate(TextPositionToPoint(new Position(_linePos, _textPos)));
- Rectangle bounds = Utility.RoundRectangle(DocumentWorkspace.VisibleDocumentRectangleF);
- bounds.Inflate(-_font.Height, -_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 - _controlKeyDownTime);
-
- // If the user taps Ctrl, then we should toggle the visiblity of the moveNub
- if (heldDuration < _controlKeyDownThreshold)
- {
- _enableNub = !_enableNub;
- }
-
- _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
- {
- switch (_mode)
- {
- case EditingMode.Editing:
- SaveHistoryMemento();
- break;
- case EditingMode.EmptyEdit:
- RedrawText(false);
- break;
- }
-
- 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;
- var cha = new CompoundHistoryMemento(Name, Image, new List<HistoryMemento>());
- _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;
- }
-
- _startTime = DateTime.Now;
-
- if (_mode != EditingMode.NotEditing && keyHandled)
- {
- RedrawText(true);
- }
- }
-
- if (!keyHandled)
- {
- base.OnKeyPress(keyData);
- }
- }
-
- private PointF TextPositionToPoint(Position p)
- {
- var 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;
- var line = (int)Math.Floor(dy / _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);
- var 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)
- {
- var newMouseXy = new Point(e.X, e.Y);
- var delta = new Size(newMouseXy.X - _startMouseXy.X, newMouseXy.Y - _startMouseXy.Y);
- _clickPoint = new Point(_startClickPoint.X + delta.Width, _startClickPoint.Y + delta.Height);
- RedrawText(false);
- UpdateStatusText();
- }
- else
- {
- bool touchingNub = _moveNub.IsPointTouching(new Point(e.X, e.Y), false);
-
- if (touchingNub && _moveNub.Visible)
- {
- Cursor = HandCursor;
- }
- else
- {
- Cursor = _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 = _moveNub.IsPointTouching(new Point(e.X, e.Y), false);
-
- if (_mode != EditingMode.NotEditing && (e.Button == MouseButtons.Right || touchingMoveNub))
- {
- _tracking = true;
- _startMouseXy = new Point(e.X, e.Y);
- _startClickPoint = _clickPoint;
- Cursor = 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 = 0 == ((ms / CursorInterval) % 2);
-
- pulseCursorState &= 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
- _moveNub.Visible = false;
- }
- else
- {
- _moveNub.Visible = true;
- }
-
- // don't show the nub while the user is moving the text around
- _moveNub.Visible &= !_tracking;
-
- // don't show the nub when the user has tapped Ctrl
- _moveNub.Visible &= _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 (_moveNub != null)
- {
- var newAlpha = (int)(sin * 255.0);
- int clampedAlpha = Utility.Clamp(newAlpha, 0, 255);
- _moveNub.Alpha = clampedAlpha;
- }
-
- PlaceMoveNub();
- }
-
- protected override void OnPasteQuery(IDataObject data, out bool canHandle)
- {
- base.OnPasteQuery(data, out canHandle);
-
- if (data.GetDataPresent(DataFormats.StringFormat, true) &&
- Active &&
- _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) || !Active || _mode == EditingMode.NotEditing)
- return;
- ++_ignoreRedraw;
- var text = (string)data.GetData(DataFormats.StringFormat, true);
-
- foreach (char c in text)
- {
- if (c == '\n')
- {
- PerformEnter();
- }
- else
- {
- PerformKeyPress(new KeyPressEventArgs(c));
- }
- }
-
- handled = true;
- --_ignoreRedraw;
-
- _moveNub.Visible = true;
-
- RedrawText(false);
- }
-
- private void InsertCharIntoString(char c)
- {
- _lines[_linePos] = ((string)_lines[_linePos]).Insert(_textPos, c.ToString());
- _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)
- {
- }
- }
- }