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)
- {
- }
- }
- }
|