OSDN Git Service

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