OSDN Git Service

SelectionChangedが同じパンドラー内で二回呼び出されることがあったので避けるようにした
[fooeditengine/FooEditEngine.git] / Core / EditView.cs
1 /*
2  * Copyright (C) 2013 FooProject
3  * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
5
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
10  */
11 using System;
12 using System.Linq;
13 using System.Collections.Generic;
14
15 namespace FooEditEngine
16 {
17     enum AdjustFlow
18     {
19         Row,
20         Col,
21         Both,
22     }
23
24     enum TextPointSearchRange
25     {
26         TextAreaOnly,
27         Full
28     }
29
30     /// <summary>
31     /// キャレットとドキュメントの表示を担当します。レイアウト関連もこちらで行います
32     /// </summary>
33     sealed class EditView : ViewBase
34
35     {
36         internal const float LineMarkerThickness = 2;
37         long tickCount;
38         bool _CaretBlink;
39         internal const int LineNumberLength = 6;
40         const int UpdateAreaPaddingWidth = 2;
41         const int UpdateAreaWidth = 4;
42         const int UpdateAreaTotalWidth = UpdateAreaWidth + UpdateAreaPaddingWidth;
43
44         /// <summary>
45         /// コンストラクター
46         /// </summary>
47         public EditView(Document doc, IEditorRender r, int MarginLeftAndRight = 5)
48             : this(doc, r, new Padding(MarginLeftAndRight, 0, MarginLeftAndRight, 0))
49         {
50         }
51
52         /// <summary>
53         /// コンストラクター
54         /// </summary>
55         /// <param name="doc">ドキュメント</param>
56         /// <param name="r">レンダー</param>
57         /// <param name="margin">マージン(1番目:左、2番目:上、3番目:右、4番目:下)</param>
58         public EditView(Document doc, IEditorRender r, Padding margin)
59             : base(doc, r, margin)
60         {
61             this.CaretBlinkTime = 500;
62             this.CaretWidthOnInsertMode = 1;
63             this.CalculateClipRect();
64             this.CaretLocation = new Point(this.render.TextArea.X, this.render.TextArea.Y);
65             this.LayoutLines.FoldingCollection.StatusChanged += FoldingCollection_StatusChanged;
66             this.IsFocused = false;
67         }
68
69         /// <summary>
70         /// 選択範囲コレクション
71         /// </summary>
72         internal SelectCollection Selections
73         {
74             get { return this.Document.Selections; }
75             set { this.Document.Selections = value; }
76         }
77
78         /// <summary>
79         /// ラインマーカーを描くなら偽。そうでなければ真
80         /// </summary>
81         public bool HideLineMarker
82         {
83             get { return this.Document.HideLineMarker; }
84             set { this.Document.HideLineMarker = value; }
85         }
86
87         /// <summary>
88         /// キャレットを描くなら偽。そうでなければ真
89         /// </summary>
90         public bool HideCaret
91         {
92             get { return this.Document.HideCaret; }
93             set { this.Document.HideCaret = value; }
94         }
95
96         /// <summary>
97         /// 挿入モードなら真を返し、上書きモードなら偽を返す
98         /// </summary>
99         public bool InsertMode
100         {
101             get { return this.Document.InsertMode; }
102             set { this.Document.InsertMode = value; }
103         }
104
105         /// <summary>
106         /// キャレットの点滅間隔
107         /// </summary>
108         public int CaretBlinkTime
109         {
110             get;
111             set;
112         }
113
114         /// <summary>
115         /// 挿入モード時のキャレットの幅
116         /// </summary>
117         public double CaretWidthOnInsertMode
118         {
119             get;
120             set;
121         }
122
123         /// <summary>
124         /// フォーカスがあるなら真をセットする
125         /// </summary>
126         public bool IsFocused
127         {
128             get;
129             set;
130         }
131
132         /// <summary>
133         /// キャレットを点滅させるなら真。そうでないなら偽
134         /// </summary>
135         /// <remarks>キャレット点滅タイマーもリセットされます</remarks>
136         public bool CaretBlink
137         {
138             get { return this._CaretBlink; }
139             set
140             {
141                 this._CaretBlink = value;
142                 if (value)
143                     this.tickCount = DateTime.Now.Ticks + this.To100nsTime(this.CaretBlinkTime);
144             }
145         }
146
147         /// <summary>
148         /// 一ページの高さに収まる行数を返す(こちらは表示されていない行も含みます)
149         /// </summary>
150         public int LineCountOnScreenWithInVisible
151         {
152             get;
153             private set;
154         }
155
156         /// <summary>
157         /// スクロール時に確保するマージン幅
158         /// </summary>
159         public double ScrollMarginWidth
160         {
161             get { return this.PageBound.Width * 20 / 100; }
162         }
163
164         /// <summary>
165         /// キャレットがある領域を示す
166         /// </summary>
167         public Point CaretLocation
168         {
169             get;
170             private set;
171         }
172
173         /// <summary>
174         /// ヒットテストを行う
175         /// </summary>
176         /// <param name="x">x座標</param>
177         /// <param name="y">y座標</param>
178         /// <returns>テキストエリア内にあれば真。そうでなければ偽</returns>
179         public bool HitTextArea(double x, double y)
180         {
181             if (x >= this.render.TextArea.X && x <= this.render.TextArea.Right &&
182                 y >= this.render.TextArea.Y && y <= this.render.TextArea.Bottom)
183                 return true;
184             else
185                 return false;
186         }
187
188         public bool IsUpperTextArea(double x, double y)
189         {
190             if (x >= this.render.TextArea.X && x <= this.render.TextArea.Right && y < this.render.TextArea.Y)
191                 return true;
192             else
193                 return false;
194         }
195
196         public bool IsUnderTextArea(double x,double y)
197         {
198             if (x >= this.render.TextArea.X && x <= this.render.TextArea.Right && y > this.render.TextArea.Bottom)
199                 return true;
200             else
201                 return false;
202         }
203
204         /// <summary>
205         /// ヒットテストを行う
206         /// </summary>
207         /// <param name="x">x座標</param>
208         /// <param name="row">行</param>
209         /// <returns>ヒットした場合はFoldingDataオブジェクトが返され、そうでない場合はnullが返る</returns>
210         public FoldingItem HitFoldingData(double x, int row)
211         {
212             IEditorRender render = (IEditorRender)base.render;
213
214             if (x >= this.GetRealtiveX(AreaType.FoldingArea) && x <= this.GetRealtiveX(AreaType.FoldingArea) + render.FoldingWidth)
215             {
216                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row);
217                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
218                 FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineHeadIndex, lineLength);
219                 if (foldingData != null && foldingData.IsFirstLine(this.LayoutLines,row))
220                     return foldingData;
221             }
222             return null;
223         }
224
225         /// <summary>
226         /// Rectで指定された範囲にドキュメントを描く
227         /// </summary>
228         /// <param name="updateRect">描写する範囲</param>
229         /// <param name="force">キャッシュした内容を使用しない場合は真を指定する</param>
230         /// <remarks>描写する範囲がPageBoundより小さい場合、キャッシュされた内容を使用することがあります。なお、レタリング後にrender.CacheContent()を呼び出さなかった場合は更新範囲にかかわらずキャッシュを使用しません</remarks>
231         public override void Draw(Rectangle updateRect,bool force = false)
232         {
233             if (this.LayoutLines.Count == 0)
234                 return;
235
236             IEditorRender render = (IEditorRender)base.render;
237
238             if ((updateRect.Height < this.PageBound.Height ||
239                 updateRect.Width < this.PageBound.Width) && 
240                 render.IsVaildCache() &&
241                 !force)
242             {
243                 render.DrawCachedBitmap(updateRect);
244             }
245             else
246             {
247                 Rectangle background = this.PageBound;
248                 render.FillBackground(background);
249
250                 if (this.Document.HideRuler == false)
251                     this.DrawRuler();
252
253                 this.DrawLineMarker(this.Document.CaretPostion.row);
254
255                 Point pos = this.render.TextArea.TopLeft;
256                 //画面上では(X,Y)が開始位置になるが、開始する行が決まっているのでオフセットを求める
257                 pos.Y -= this.Src.GetOffsetY(this.render.emSize.Height);
258
259                 double endposy = this.render.TextArea.Bottom;
260                 Size lineNumberSize = new Size(this.render.LineNemberWidth, this.render.TextArea.Height);
261
262                 this.render.BeginClipRect(new Rectangle(this.PageBound.X, this.render.TextArea.Y, this.PageBound.Width, this.render.TextArea.Height));
263
264                 //パフォーマンス向上のため行番号などを先に描く
265                 for (int i = this.Src.Row; i < this.LayoutLines.Count; i++)
266                 {
267                     int lineIndex = this.LayoutLines.GetIndexFromLineNumber(i);
268                     int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
269                     ITextLayout layout = this.LayoutLines.GetLayout(i);
270
271                     if (pos.Y > endposy)
272                         break;
273
274                     FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineIndex, lineLength);
275
276                     if (foldingData != null)
277                     {
278                         if ((!this.LayoutLines.FoldingCollection.IsHasParent(foldingData) || 
279                              !this.LayoutLines.FoldingCollection.IsParentHidden(foldingData))
280                             && foldingData.IsFirstLine(this.LayoutLines, i))
281                             render.DrawFoldingMark(foldingData.Expand, this.PageBound.X + this.GetRealtiveX(AreaType.FoldingArea), pos.Y);
282                         if (this.LayoutLines.FoldingCollection.IsHidden(lineIndex))
283                             continue;
284                     }
285
286                     if (this.Document.DrawLineNumber)
287                     {
288                         this.render.DrawString((i + 1).ToString(), this.PageBound.X + this.GetRealtiveX(AreaType.LineNumberArea), pos.Y, StringAlignment.Right, lineNumberSize,StringColorType.LineNumber);
289                     }
290
291                     DrawUpdateArea(i, pos.Y);
292
293                     pos.Y += this.LayoutLines.GetLayout(i).Height;
294                     //pos.Y += this.render.emSize.Height;
295                 }
296
297                 this.render.EndClipRect();
298
299                 //リセットしないと行が正しく描けない
300                 pos = this.render.TextArea.TopLeft;
301                 pos.X -= this.Src.X;
302                 pos.Y -= this.Src.GetOffsetY(this.render.emSize.Height);
303
304                 this.render.BeginClipRect(this.render.TextArea);
305
306                 for (int i = this.Src.Row; i < this.LayoutLines.Count; i++)
307                 {
308                     int lineIndex = this.LayoutLines.GetIndexFromLineNumber(i);
309                     int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
310                     ITextLayout layout = this.LayoutLines.GetLayout(i);
311
312                     if (pos.Y > endposy)
313                         break;
314
315                     FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineIndex, lineLength);
316
317                     if (foldingData != null)
318                     {
319                         if (this.LayoutLines.FoldingCollection.IsHidden(lineIndex))
320                             continue;
321                     }
322
323                     var selectRange = from s in this.Selections.Get(lineIndex, lineLength)
324                                       let n = Util.ConvertAbsIndexToRelIndex(s, lineIndex, lineLength)
325                                       select n;
326
327                     this.render.DrawOneLine(this.LayoutLines, i, pos.X, pos.Y, selectRange);
328
329                     pos.Y += this.LayoutLines.GetLayout(i).Height;
330                     //pos.Y += this.render.emSize.Height;
331                 }
332
333                 this.render.EndClipRect();
334
335                 this.DrawInsertPoint();
336
337                 this.Document.SelectGrippers.BottomLeft.Draw(this.render);
338                 this.Document.SelectGrippers.BottomRight.Draw(this.render);
339
340                 render.CacheContent();
341             }
342
343              this.DrawCaret();
344         }
345
346         void DrawUpdateArea(int row,double ypos)
347         {
348             IEditorRender render = (IEditorRender)base.render;
349             if(this.LayoutLines.GetDirtyFlag(row))
350             {
351                 Point pos = new Point(this.PageBound.X + this.GetRealtiveX(AreaType.UpdateArea), ypos);
352                 Rectangle rect = new Rectangle(pos.X, pos.Y, UpdateAreaWidth, this.LayoutLines.GetLayout(row).Height);
353                 render.FillRectangle(rect, FillRectType.UpdateArea);
354             }
355         }
356
357         void DrawRuler()
358         {
359             IEditorRender render = (IEditorRender)base.render;
360
361             Point pos, from, to;
362             Size emSize = render.emSize;
363             Rectangle clipRect = this.render.TextArea;
364             int count = 0;
365             double markerHeight = emSize.Height / 2;
366             if (this.Document.RightToLeft)
367             {
368                 pos = new Point(clipRect.TopRight.X, clipRect.TopRight.Y - emSize.Height - LineMarkerThickness);
369                 for (; pos.X >= clipRect.TopLeft.X; pos.X -= emSize.Width, count++)
370                 {
371                     from = pos;
372                     to = new Point(pos.X, pos.Y + emSize.Height);
373                     int mod = count % 10;
374                     if (mod == 0)
375                     {
376                         string countStr = (count / 10).ToString();
377                         double counterWidth = emSize.Width * countStr.Length;
378                         this.render.DrawString(countStr, pos.X - counterWidth, pos.Y, StringAlignment.Right, new Size(counterWidth, double.MaxValue));
379                     }
380                     else if (mod == 5)
381                         from.Y = from.Y + emSize.Height / 2;
382                     else
383                         from.Y = from.Y + emSize.Height * 3 / 4;
384                     render.DrawLine(from, to);
385                     if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width)
386                         render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret);
387                 }
388             }
389             else
390             {
391                 pos = new Point(clipRect.TopLeft.X, clipRect.TopLeft.Y - emSize.Height - LineMarkerThickness);
392                 for (; pos.X < clipRect.TopRight.X; pos.X += emSize.Width, count++)
393                 {
394                     from = pos;
395                     to = new Point(pos.X, pos.Y + emSize.Height);
396                     int mod = count % 10;
397                     if (mod == 0)
398                         this.render.DrawString((count / 10).ToString(), pos.X, pos.Y, StringAlignment.Left, new Size(double.MaxValue, double.MaxValue));
399                     else if (mod == 5)
400                         from.Y = from.Y + emSize.Height / 2;
401                     else
402                         from.Y = from.Y + emSize.Height * 3 / 4;
403                     render.DrawLine(from, to);
404                     if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width)
405                         render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret);
406                 }
407             }
408             from = clipRect.TopLeft;
409             from.Y -= LineMarkerThickness;
410             to = clipRect.TopRight;
411             to.Y -= LineMarkerThickness;
412             render.DrawLine(from, to);
413         }
414
415         void DrawInsertPoint()
416         {
417             //一つしかない場合は行選択の可能性がある
418             if (this.Selections.Count <= 1)
419                 return;
420             IEditorRender render = (IEditorRender)base.render;
421             foreach (Selection sel in this.Selections)
422             {
423                 if (sel.length == 0)
424                 {
425                     TextPoint tp = this.GetLayoutLineFromIndex(sel.start);
426                     Point left = this.GetPostionFromTextPoint(tp);
427                     double lineHeight = this.LayoutLines.GetLayout(tp.row).Height;
428                     Rectangle InsertRect = new Rectangle(left.X,
429                         left.Y,
430                         CaretWidthOnInsertMode,
431                         lineHeight);
432                     render.FillRectangle(InsertRect, FillRectType.InsertPoint);
433                 }
434             }
435         }
436
437         bool DrawCaret()
438         {
439             if (this.HideCaret || !this.IsFocused)
440                 return false;
441
442             long diff = DateTime.Now.Ticks - this.tickCount;
443             long blinkTime = this.To100nsTime(this.CaretBlinkTime);
444
445             if (this.CaretBlink && diff % blinkTime >= blinkTime / 2)
446                 return false;
447
448             Rectangle CaretRect = new Rectangle();
449
450             IEditorRender render = (IEditorRender)base.render;
451
452             int row = this.Document.CaretPostion.row;
453             double lineHeight = this.LayoutLines.GetLayout(row).Height;
454             double charWidth = this.LayoutLines.GetLayout(row).GetWidthFromIndex(this.Document.CaretPostion.col);
455
456             if (this.InsertMode || charWidth == 0)
457             {
458                 CaretRect.Size = new Size(CaretWidthOnInsertMode, lineHeight);
459                 CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y);
460                 render.FillRectangle(CaretRect, FillRectType.InsertCaret);
461             }
462             else
463             {
464                 double height = lineHeight / 3;
465                 CaretRect.Size = new Size(charWidth, height);
466                 CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y + lineHeight - height);
467                 render.FillRectangle(CaretRect, FillRectType.OverwriteCaret);
468             }
469             return true;
470         }
471
472         long To100nsTime(int ms)
473         {
474             return ms * 10000;
475         }
476
477         public void DrawLineMarker(int row)
478         {
479             if (this.HideLineMarker || !this.IsFocused)
480                 return;
481             IEditorRender render = (IEditorRender)base.render;
482             Point p = this.CaretLocation;
483             double height = this.LayoutLines.GetLayout(this.Document.CaretPostion.row).Height;
484             double width = this.render.TextArea.Width;
485             render.FillRectangle(new Rectangle(this.PageBound.X + this.render.TextArea.X, this.CaretLocation.Y, width, height), FillRectType.LineMarker);
486         }
487
488         /// <summary>
489         /// 現在のキャレット位置の領域を返す
490         /// </summary>
491         /// <returns>矩形領域を表すRectangle</returns>
492         public Rectangle GetCurrentCaretRect()
493         {
494             ITextLayout layout = this.LayoutLines.GetLayout(this.Document.CaretPostion.row);
495             double width = layout.GetWidthFromIndex(this.Document.CaretPostion.col);
496             if (width == 0.0)
497                 width = this.CaretWidthOnInsertMode;
498             double height = layout.Height;
499             Rectangle updateRect = new Rectangle(
500                 this.CaretLocation.X,
501                 this.CaretLocation.Y,
502                 width,
503                 height);
504             return updateRect;
505         }
506
507         /// <summary>
508         /// 指定した座標の一番近くにあるTextPointを取得する
509         /// </summary>
510         /// <param name="p">テキストエリアを左上とする相対位置</param>
511         /// <param name="searchRange">探索範囲</param>
512         /// <returns>レイアウトラインを指し示すTextPoint</returns>
513         public TextPoint GetTextPointFromPostion(Point p,TextPointSearchRange searchRange = TextPointSearchRange.TextAreaOnly)
514         {
515             if(searchRange == TextPointSearchRange.TextAreaOnly)
516             {
517                 if (p.Y < this.render.TextArea.TopLeft.Y ||
518                     p.Y > this.render.TextArea.BottomRight.Y)
519                     return TextPoint.Null;
520             }
521
522             TextPoint tp = new TextPoint();
523
524             if (this.LayoutLines.Count == 0)
525                 return tp;
526
527             p.Y -= this.render.TextArea.Y;
528
529             int lineHeadIndex, lineLength;
530
531             if(p.Y >= this.render.TextArea.TopLeft.Y)
532             {
533                 double y = 0;
534                 tp.row = this.LayoutLines.Count - 1;
535                 for (int i = this.Src.Row; i < this.LayoutLines.Count; i++)
536                 {
537                     double height = this.LayoutLines.GetLayout(i).Height;
538
539                     lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
540                     lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
541
542                     if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
543                         continue;
544
545                     if (y + height > p.Y)
546                     {
547                         tp.row = i;
548                         break;
549                     }
550                     y += height;
551                 }
552             }else
553             {
554                 double y = this.render.TextArea.TopLeft.Y;
555                 tp.row = 0;
556                 for (int i = this.Src.Row; i >= 0; i--)
557                 {
558                     double height = this.LayoutLines.GetLayout(i).Height;
559
560                     lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
561                     lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
562
563                     if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
564                         continue;
565
566                     if (y - height < p.Y)
567                     {
568                         tp.row = i;
569                         break;
570                     }
571                     y -= height;
572                 }
573             }
574
575             if (searchRange == TextPointSearchRange.TextAreaOnly)
576             {
577                 if (p.X < this.render.TextArea.X)
578                     return tp;
579             }
580
581             tp.col = GetIndexFromColPostion(tp.row, p.X);
582
583             lineLength = this.LayoutLines.GetLengthFromLineNumber(tp.row);
584             if (tp.col > lineLength)
585                 tp.col = lineLength;
586
587             return tp;
588         }
589
590         /// <summary>
591         /// 桁方向の座標に対応するインデックスを取得する
592         /// </summary>
593         /// <param name="row">対象となる行</param>
594         /// <param name="x">テキストエリアからの相対位置</param>
595         /// <returns></returns>
596         public int GetIndexFromColPostion(int row, double x)
597         {
598             x -= this.render.TextArea.X;
599             int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
600             if (lineLength == 0)
601                 return 0;
602             int index = this.LayoutLines.GetLayout(row).GetIndexFromColPostion(this.Src.X + x);
603             return index;
604         }
605
606         /// <summary>
607         /// インデックスに対応する桁方向の座標を得る
608         /// </summary>
609         /// <param name="row">対象となる行</param>
610         /// <param name="index">インデックス</param>
611         /// <returns>テキストエリアからの相対位置を返す</returns>
612         public double GetColPostionFromIndex(int row, int index)
613         {
614             double x = this.LayoutLines.GetLayout(row).GetColPostionFromIndex(index);
615             return x - Src.X + this.render.TextArea.X;
616         }
617
618         /// <summary>
619         /// TextPointに対応する座標を得る
620         /// </summary>
621         /// <param name="tp">レイアウトライン上の位置</param>
622         /// <returns>テキストエリアを左上とする相対位置</returns>
623         public Point GetPostionFromTextPoint(TextPoint tp)
624         {
625             Point p = new Point();
626             for (int i = this.Src.Row; i < tp.row; i++)
627             {
628                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
629                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
630                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
631                     continue;
632                 p.Y += this.LayoutLines.GetLayout(i).Height;
633             }
634             p.X = this.GetColPostionFromIndex(tp.row, tp.col);
635             p.Y += this.render.TextArea.Y;
636             return p;
637         }
638
639         public Gripper HitGripperFromPoint(Point p)
640         {
641             if (this.Document.SelectGrippers.BottomLeft.IsHit(p))
642                 return this.Document.SelectGrippers.BottomLeft;
643             if (this.Document.SelectGrippers.BottomRight.IsHit(p))
644                 return this.Document.SelectGrippers.BottomRight;
645             return null;
646         }
647
648         public Rectangle GetRectFromIndex(int index,int width,int height)
649         {
650             TextPoint tp = this.LayoutLines.GetTextPointFromIndex(index);
651             return this.GetRectFromTextPoint(tp, width, height);
652         }
653
654         public Rectangle GetRectFromTextPoint(TextPoint tp, int width, int height)
655         {
656             if (tp.row < this.Src.Row)
657                 return Rectangle.Empty;
658             double radius = width / 2;
659             Point point = this.GetPostionFromTextPoint(tp);
660             double lineHeight = this.LayoutLines.GetLayout(tp.row).Height;
661             double srcOffsetY = this.Src.GetOffsetY(this.render.emSize.Height); //画面上ではずれているので引く必要がある
662
663             Rectangle rect =  new Rectangle(point.X - radius, point.Y + lineHeight - srcOffsetY, width, height);
664
665             if (rect.BottomLeft.Y >= this.render.TextArea.BottomLeft.Y ||
666                 rect.BottomRight.X < this.render.TextArea.BottomLeft.X ||
667                 rect.BottomLeft.X > this.render.TextArea.BottomRight.X)
668                 return Rectangle.Empty;
669             return rect;
670         }
671
672         /// <summary>
673         /// キャレットを指定した位置に移動させる
674         /// </summary>
675         /// <param name="row"></param>
676         /// <param name="col"></param>
677         /// <param name="autoExpand">折り畳みを展開するなら真</param>
678         public void JumpCaret(int row, int col, bool autoExpand = true)
679         {
680             if (autoExpand)
681             {
682                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row);
683                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
684                 FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineHeadIndex, lineLength);
685                 if(foldingData != null)
686                 {
687                     if (this.LayoutLines.FoldingCollection.IsParentHidden(foldingData) || !foldingData.IsFirstLine(this.LayoutLines, row))
688                     {
689                         this.LayoutLines.FoldingCollection.Expand(foldingData);
690                     }
691                 }
692             }
693
694             //イベント呼び出しの再入防止のため
695             this.Document.SetCaretPostionWithoutEvent(new TextPoint(row, col));
696         }
697
698         /// <summary>
699         /// index上の文字が表示されるようにSrcを調整する
700         /// </summary>
701         /// <param name="index">インデックス</param>
702         /// <returns>調整されたら真。そうでなければ偽</returns>
703         public bool AdjustSrc(int index)
704         {
705             TextPoint startTextPoint = this.GetLayoutLineFromIndex(index);
706             double x = this.LayoutLines.GetLayout(startTextPoint.row).GetColPostionFromIndex(startTextPoint.col);
707             if (x < this.Src.X ||
708                 x > this.Src.X + this.PageBound.Width)
709             {
710                 this.TryScroll(x, this.Src.Row);
711                 return true;
712             }
713             if (startTextPoint.row < this.Src.Row ||
714                 startTextPoint.row > this.Src.Row + this.LineCountOnScreenWithInVisible)
715             {
716                 this.TryScroll(this.Src.X, startTextPoint.row);
717                 return true;
718             }
719             return false;
720         }
721
722         /// <summary>
723         /// キャレットがあるところまでスクロールする
724         /// </summary>
725         /// <return>再描写する必要があるなら真を返す</return>
726         /// <remarks>Document.Update(type == UpdateType.Clear)イベント時に呼び出した場合、例外が発生します</remarks>
727         public bool AdjustCaretAndSrc(AdjustFlow flow = AdjustFlow.Both)
728         {
729             IEditorRender render = (IEditorRender)base.render;
730
731             if (this.PageBound.Width == 0 || this.PageBound.Height == 0)
732             {
733                 this.SetCaretPostion(this.Padding.Left + render.FoldingWidth, 0);
734                 return false;
735             }
736
737             bool result = false;
738             TextPoint tp = this.Document.CaretPostion;
739             double x = this.CaretLocation.X;
740             double y = this.CaretLocation.Y;
741
742             if (flow == AdjustFlow.Col || flow == AdjustFlow.Both)
743             {
744                 x = this.LayoutLines.GetLayout(tp.row).GetColPostionFromIndex(tp.col);
745
746                 double left = this.Src.X;
747                 double right = this.Src.X + this.render.TextArea.Width;
748
749                 if (x >= left && x <= right)    //xは表示領域にないにある
750                 {
751                     x -= left;
752                 }
753                 else if (x > right) //xは表示領域の右側にある
754                 {
755                     this.Document.Src = new SrcPoint(x - this.render.TextArea.Width + this.ScrollMarginWidth,this.Document.Src.Row,this.Document.Src.Y);
756                     if (this.Document.RightToLeft && this.Document.Src.X > 0)
757                     {
758                         System.Diagnostics.Debug.Assert(x > 0);
759                         this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Document.Src.Y);
760                     }
761                     else
762                     {
763                         x = this.render.TextArea.Width - this.ScrollMarginWidth;
764                     }
765                     result = true;
766                 }
767                 else if (x < left)    //xは表示領域の左側にある
768                 {
769                     this.Document.Src = new SrcPoint(x - this.ScrollMarginWidth, this.Document.Src.Row, this.Document.Src.Y);
770                     if (!this.Document.RightToLeft && this.Document.Src.X < this.render.TextArea.X)
771                     {
772                         this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Document.Src.Y);
773                     }
774                     else
775                     {
776                         x = this.ScrollMarginWidth;
777                     }
778                     result = true;
779                 }
780                 x += this.render.TextArea.X;
781             }
782
783             if (flow == AdjustFlow.Row || flow == AdjustFlow.Both)
784             {
785                 int caretRow = 0;
786                 int lineCount = this.LineCountOnScreenWithInVisible;
787                 if (tp.row >= this.Src.Row && tp.row < this.Src.Row + lineCount)
788                 {
789                     caretRow = tp.row - this.Src.Row;
790                     y = -this.Src.GetOffsetY(this.render.emSize.Height);    //画面上ではずれているので引く必要がある
791                 }
792                 else if (tp.row >= this.Src.Row + lineCount)
793                 {
794                     int srcRow = this.GetSrcRow(tp.row, this.LineCountOnScreen);
795                     this.Document.Src = new SrcPoint(this.Document.Src.X, srcRow, srcRow * this.render.emSize.Height);
796                     caretRow = tp.row - this.Document.Src.Row;
797                     y = 0;
798                     result = true;
799                     CalculateLineCountOnScreen();
800                 }
801                 else if (tp.row < this.Src.Row)
802                 {
803                     this.Document.Src = new SrcPoint(this.Document.Src.X, tp.row, tp.row * this.render.emSize.Height);
804                     y = 0;
805                     result = true;
806                     CalculateLineCountOnScreen();
807                 }
808
809                 if (caretRow > 0)
810                 {
811                     for (int i = 0; i < caretRow; i++)
812                     {
813                         int currentRow = this.Src.Row + i;
814                         int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(currentRow);
815                         int lineLength = this.LayoutLines.GetLengthFromLineNumber(currentRow);
816
817                         if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
818                             continue;
819
820                         y += this.LayoutLines.GetLayout(currentRow).Height;
821                     }
822                 }
823                 y += this.render.TextArea.Y;
824             }
825
826             this.SetCaretPostion(x, y);
827
828             if (result)
829             {
830                 this.OnSrcChanged(null);
831             }
832
833             return result;
834         }
835
836         int GetSrcRow(int row,int count)
837         {
838             if (this.LayoutLines.FoldingStrategy == null)
839                 return row - count;
840             for (int i = row; i >= 0; i--)
841             {
842                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
843                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
844                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
845                     continue;
846                 if (count <= 0)
847                     return i;
848                 count--;
849             }
850             return 0;
851         }
852
853         /// <summary>
854         /// レイアウト行をテキストポイントからインデックスに変換する
855         /// </summary>
856         /// <param name="tp">テキストポイント表す</param>
857         /// <returns>インデックスを返す</returns>
858         public int GetIndexFromLayoutLine(TextPoint tp)
859         {
860             return this.LayoutLines.GetIndexFromTextPoint(tp);
861         }
862
863         /// <summary>
864         /// インデックスからレイアウト行を指し示すテキストポイントに変換する
865         /// </summary>
866         /// <param name="index">インデックスを表す</param>
867         /// <returns>テキストポイント返す</returns>
868         public TextPoint GetLayoutLineFromIndex(int index)
869         {
870             return this.LayoutLines.GetTextPointFromIndex(index);
871         }
872
873         /// <summary>
874         /// 指定した座標までスクロールする
875         /// </summary>
876         /// <param name="x"></param>
877         /// <param name="row"></param>
878         /// <remarks>
879         /// 範囲外の座標を指定した場合、範囲内に収まるように調整されます
880         /// </remarks>
881         public void Scroll(double x, int row)
882         {
883             if (x < 0)
884                 x = 0;
885             if(row < 0)
886                 row = 0;
887             int endRow = this.LayoutLines.Count - 1 - this.LineCountOnScreen;
888             if (endRow < 0)
889                 endRow = 0;
890             if (row > endRow)
891                 row = endRow;
892             base.TryScroll(x, row);
893         }
894
895         /// <summary>
896         /// 指定行までスクロールする
897         /// </summary>
898         /// <param name="row">行</param>
899         /// <param name="alignTop">指定行を画面上に置くなら真。そうでないなら偽</param>
900         public void ScrollIntoView(int row, bool alignTop)
901         {
902             this.Scroll(0, row);
903             if (alignTop)
904                 return;
905             double y = this.render.TextArea.Height;
906             for (int i = row; i >= 0; i--)
907             {
908                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
909                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
910                 double height = this.LayoutLines.GetLayout(i).Height;
911                 if (y - height <= 0)
912                 {
913                     this.Scroll(0, i);
914                 }
915                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
916                     continue;
917                 y -= height;
918             }
919         }
920
921         public int AdjustRow(int row, bool isMoveNext)
922         {
923             if (this.LayoutLines.FoldingStrategy == null)
924                 return row;
925             int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row);
926             int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
927             FoldingItem foldingData = this.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength);
928             if (foldingData != null && !foldingData.Expand)
929             {
930                 if (foldingData.End == this.Document.Length)
931                     return row;
932                 if (isMoveNext && lineHeadIndex > foldingData.Start)
933                     row = this.LayoutLines.GetLineNumberFromIndex(foldingData.End) + 1;
934                 else
935                     row = this.LayoutLines.GetLineNumberFromIndex(foldingData.Start);
936                 if(row > this.LayoutLines.Count - 1)
937                     row = this.LayoutLines.GetLineNumberFromIndex(foldingData.Start);
938             }
939             return row;
940         }
941
942         protected override void CalculateClipRect()
943         {
944             IEditorRender render = (IEditorRender)base.render;
945             double x, y, width, height;
946
947             if (this.Document.DrawLineNumber)
948             {
949                 if (this.Document.RightToLeft)
950                     x = this.Padding.Left;
951                 else
952                     x = this.Padding.Left + UpdateAreaTotalWidth + this.render.LineNemberWidth + this.LineNumberMargin + render.FoldingWidth;
953                 width = this.PageBound.Width - this.render.LineNemberWidth - this.LineNumberMargin - this.Padding.Left - this.Padding.Right - render.FoldingWidth - UpdateAreaTotalWidth;
954             }
955             else
956             {
957                 if (this.Document.RightToLeft)
958                     x = this.Padding.Left;
959                 else
960                     x = this.Padding.Left + UpdateAreaTotalWidth + render.FoldingWidth;
961                 width = this.PageBound.Width - this.Padding.Left - this.Padding.Right - render.FoldingWidth - UpdateAreaTotalWidth;
962             }
963
964             y = this.Padding.Top;
965             height = this.PageBound.Height - this.Padding.Top - this.Padding.Bottom;
966
967             if (this.Document.HideRuler == false)
968             {
969                 double rulerHeight = this.render.emSize.Height + LineMarkerThickness;
970                 y += rulerHeight;
971                 height -= rulerHeight;
972             }
973
974             if (width < 0)
975                 width = 0;
976
977             if (height < 0)
978                 height = 0;
979
980             this.render.TextArea = new Rectangle(x, y, width, height);
981
982             this.LineBreakingMarginWidth = width * 5 / 100;
983         }
984
985         public override void CalculateLineCountOnScreen()
986         {
987             if (this.LayoutLines.Count == 0 || this.PageBound.Height == 0)
988                 return;
989
990             double y = 0;
991             int i = this.Src.Row;
992             int visualCount = this.Src.Row;
993             for (; true; i++)
994             {
995                 int row = i < this.LayoutLines.Count ? i : this.LayoutLines.Count - 1;
996
997                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row);
998                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
999
1000                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex) && row < this.LayoutLines.Count - 1)
1001                     continue;
1002
1003                 ITextLayout layout = this.LayoutLines.GetLayout(row);
1004
1005                 double width = layout.Width;
1006
1007                 if (width > this._LongestWidth)
1008                     this._LongestWidth = width;
1009
1010                 double lineHeight = layout.Height;
1011
1012                 y += lineHeight;
1013
1014                 if (y >= this.render.TextArea.Height)
1015                     break;
1016                 visualCount++;
1017             }
1018             this.LineCountOnScreen = Math.Max(visualCount - this.Src.Row - 1, 0);
1019             this.LineCountOnScreenWithInVisible = Math.Max(i - this.Src.Row - 1, 0);
1020         }
1021
1022         void SetCaretPostion(double x, double y)
1023         {
1024             this.CaretLocation = new Point(x + this.PageBound.X, y + this.PageBound.Y);
1025         }
1026
1027         void FoldingCollection_StatusChanged(object sender, FoldingItemStatusChangedEventArgs e)
1028         {
1029             this.CalculateLineCountOnScreen();
1030         }
1031
1032         enum AreaType
1033         {
1034             UpdateArea,
1035             FoldingArea,
1036             LineNumberArea,
1037             TextArea
1038         }
1039
1040         double GetRealtiveX(AreaType type)
1041         {
1042             IEditorRender render = (IEditorRender)base.render;
1043             switch (type)
1044             {
1045                 case AreaType.UpdateArea:
1046                     if (this.Document.RightToLeft)
1047                         return this.PageBound.TopRight.X - UpdateAreaTotalWidth;
1048                     if (this.Document.DrawLineNumber)
1049                         return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin - render.FoldingWidth - UpdateAreaTotalWidth;
1050                     else
1051                         return this.render.TextArea.X - render.FoldingWidth - UpdateAreaTotalWidth;
1052                 case AreaType.FoldingArea:
1053                     if (this.Document.RightToLeft)
1054                         return this.PageBound.TopRight.X - render.FoldingWidth;
1055                     if (this.Document.DrawLineNumber)
1056                         return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin - render.FoldingWidth;
1057                     else
1058                         return this.render.TextArea.X - render.FoldingWidth;
1059                 case AreaType.LineNumberArea:
1060                     if (this.Document.DrawLineNumber == false)
1061                         throw new InvalidOperationException();
1062                     if (this.Document.RightToLeft)
1063                         return this.PageBound.TopRight.X - UpdateAreaTotalWidth - render.FoldingWidth - this.render.LineNemberWidth;
1064                     else
1065                         return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin;
1066                 case AreaType.TextArea:
1067                     return this.render.TextArea.X;
1068             }
1069             throw new ArgumentOutOfRangeException();
1070         }
1071     }
1072 }