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     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                 //画面上では行をずらして表示する
257                 pos.Y += this.Src.OffsetY;
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.IsHidden(lineIndex))
279                             continue;
280                         if (foldingData.IsFirstLine(this.LayoutLines, i))
281                             render.DrawFoldingMark(foldingData.Expand, this.PageBound.X + this.GetRealtiveX(AreaType.FoldingArea), pos.Y);
282                     }
283
284                     if (this.Document.DrawLineNumber)
285                     {
286                         this.render.DrawString((i + 1).ToString(), this.PageBound.X + this.GetRealtiveX(AreaType.LineNumberArea), pos.Y, StringAlignment.Right, lineNumberSize,StringColorType.LineNumber);
287                     }
288
289                     DrawUpdateArea(i, pos.Y);
290
291                     pos.Y += layout.Height;
292                     //pos.Y += this.render.emSize.Height;
293                 }
294
295                 this.render.EndClipRect();
296
297                 //リセットしないと行が正しく描けない
298                 pos = this.render.TextArea.TopLeft;
299                 pos.X -= this.Src.X;
300                 pos.Y += this.Src.OffsetY;
301
302                 this.render.BeginClipRect(this.render.TextArea);
303
304                 for (int i = this.Src.Row; i < this.LayoutLines.Count; i++)
305                 {
306                     int lineIndex = this.LayoutLines.GetIndexFromLineNumber(i);
307                     int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
308                     ITextLayout layout = this.LayoutLines.GetLayout(i);
309
310                     if (pos.Y > endposy)
311                         break;
312
313                     FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineIndex, lineLength);
314
315                     if (foldingData != null)
316                     {
317                         if (this.LayoutLines.FoldingCollection.IsHidden(lineIndex))
318                             continue;
319                     }
320
321                     this.render.DrawOneLine(this.Document, this.LayoutLines, i, pos.X, pos.Y);
322
323                     pos.Y += layout.Height;
324                 }
325
326                 this.render.EndClipRect();
327
328                 this.DrawInsertPoint();
329
330                 this.Document.SelectGrippers.BottomLeft.Draw(this.render);
331                 this.Document.SelectGrippers.BottomRight.Draw(this.render);
332
333                 render.CacheContent();
334             }
335
336              this.DrawCaret();
337         }
338
339         void DrawUpdateArea(int row,double ypos)
340         {
341             IEditorRender render = (IEditorRender)base.render;
342             if(this.LayoutLines.GetDirtyFlag(row))
343             {
344                 Point pos = new Point(this.PageBound.X + this.GetRealtiveX(AreaType.UpdateArea), ypos);
345                 Rectangle rect = new Rectangle(pos.X, pos.Y, UpdateAreaWidth, this.LayoutLines.GetLayout(row).Height);
346                 render.FillRectangle(rect, FillRectType.UpdateArea);
347             }
348         }
349
350         void DrawRuler()
351         {
352             IEditorRender render = (IEditorRender)base.render;
353
354             Point pos, from, to;
355             Size emSize = render.emSize;
356             Rectangle clipRect = this.render.TextArea;
357             int count = 0;
358             double markerHeight = emSize.Height / 2;
359             if (this.Document.RightToLeft)
360             {
361                 pos = new Point(clipRect.TopRight.X, clipRect.TopRight.Y - emSize.Height - LineMarkerThickness);
362                 for (; pos.X >= clipRect.TopLeft.X; pos.X -= emSize.Width, count++)
363                 {
364                     from = pos;
365                     to = new Point(pos.X, pos.Y + emSize.Height);
366                     int mod = count % 10;
367                     if (mod == 0)
368                     {
369                         string countStr = (count / 10).ToString();
370                         double counterWidth = emSize.Width * countStr.Length;
371                         this.render.DrawString(countStr, pos.X - counterWidth, pos.Y, StringAlignment.Right, new Size(counterWidth, double.MaxValue));
372                     }
373                     else if (mod == 5)
374                         from.Y = from.Y + emSize.Height / 2;
375                     else
376                         from.Y = from.Y + emSize.Height * 3 / 4;
377                     render.DrawLine(from, to);
378                     if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width)
379                         render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret);
380                 }
381             }
382             else
383             {
384                 pos = new Point(clipRect.TopLeft.X, clipRect.TopLeft.Y - emSize.Height - LineMarkerThickness);
385                 for (; pos.X < clipRect.TopRight.X; pos.X += emSize.Width, count++)
386                 {
387                     from = pos;
388                     to = new Point(pos.X, pos.Y + emSize.Height);
389                     int mod = count % 10;
390                     if (mod == 0)
391                         this.render.DrawString((count / 10).ToString(), pos.X, pos.Y, StringAlignment.Left, new Size(double.MaxValue, double.MaxValue));
392                     else if (mod == 5)
393                         from.Y = from.Y + emSize.Height / 2;
394                     else
395                         from.Y = from.Y + emSize.Height * 3 / 4;
396                     render.DrawLine(from, to);
397                     if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width)
398                         render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret);
399                 }
400             }
401             from = clipRect.TopLeft;
402             from.Y -= LineMarkerThickness;
403             to = clipRect.TopRight;
404             to.Y -= LineMarkerThickness;
405             render.DrawLine(from, to);
406         }
407
408         void DrawInsertPoint()
409         {
410             //一つしかない場合は行選択の可能性がある
411             if (this.Selections.Count <= 1)
412                 return;
413             IEditorRender render = (IEditorRender)base.render;
414             foreach (Selection sel in this.Selections)
415             {
416                 if (sel.length == 0)
417                 {
418                     TextPoint tp = this.GetLayoutLineFromIndex(sel.start);
419                     Point left = this.GetPostionFromTextPoint(tp);
420                     double lineHeight = render.emSize.Height;
421                     Rectangle InsertRect = new Rectangle(left.X,
422                         left.Y,
423                         CaretWidthOnInsertMode,
424                         lineHeight);
425                     render.FillRectangle(InsertRect, FillRectType.InsertPoint);
426                 }
427             }
428         }
429
430         bool DrawCaret()
431         {
432             if (this.HideCaret || !this.IsFocused)
433                 return false;
434
435             long diff = DateTime.Now.Ticks - this.tickCount;
436             long blinkTime = this.To100nsTime(this.CaretBlinkTime);
437
438             if (this.CaretBlink && diff % blinkTime >= blinkTime / 2)
439                 return false;
440
441             Rectangle CaretRect = new Rectangle();
442
443             IEditorRender render = (IEditorRender)base.render;
444
445             int row = this.Document.CaretPostion.row;
446             ITextLayout layout = this.LayoutLines.GetLayout(row);
447             double lineHeight = render.emSize.Height;
448             double charWidth = layout.GetWidthFromIndex(this.Document.CaretPostion.col);
449
450             if (this.InsertMode || charWidth == 0)
451             {
452                 CaretRect.Size = new Size(CaretWidthOnInsertMode, lineHeight);
453                 CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y);
454                 render.FillRectangle(CaretRect, FillRectType.InsertCaret);
455             }
456             else
457             {
458                 double height = lineHeight / 3;
459                 CaretRect.Size = new Size(charWidth, height);
460                 CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y + lineHeight - height);
461                 render.FillRectangle(CaretRect, FillRectType.OverwriteCaret);
462             }
463             return true;
464         }
465
466         long To100nsTime(int ms)
467         {
468             return ms * 10000;
469         }
470
471         public void DrawLineMarker(int row)
472         {
473             if (this.HideLineMarker || !this.IsFocused)
474                 return;
475             IEditorRender render = (IEditorRender)base.render;
476             Point p = this.CaretLocation;
477             double height = this.LayoutLines.GetLayout(this.Document.CaretPostion.row).Height;
478             double width = this.render.TextArea.Width;
479             render.FillRectangle(new Rectangle(this.PageBound.X + this.render.TextArea.X, this.CaretLocation.Y, width, height), FillRectType.LineMarker);
480         }
481
482         /// <summary>
483         /// 現在のキャレット位置の領域を返す
484         /// </summary>
485         /// <returns>矩形領域を表すRectangle</returns>
486         public Rectangle GetCurrentCaretRect()
487         {
488             ITextLayout layout = this.LayoutLines.GetLayout(this.Document.CaretPostion.row);
489             double width = layout.GetWidthFromIndex(this.Document.CaretPostion.col);
490             if (width == 0.0)
491                 width = this.CaretWidthOnInsertMode;
492             double height = layout.Height;
493             Rectangle updateRect = new Rectangle(
494                 this.CaretLocation.X,
495                 this.CaretLocation.Y,
496                 width,
497                 height);
498             return updateRect;
499         }
500
501         /// <summary>
502         /// 指定した座標の一番近くにあるTextPointを取得する
503         /// </summary>
504         /// <param name="p">テキストエリアを左上とする相対位置</param>
505         /// <param name="searchRange">探索範囲</param>
506         /// <returns>レイアウトラインを指し示すTextPoint</returns>
507         public TextPoint GetTextPointFromPostion(Point p,TextPointSearchRange searchRange = TextPointSearchRange.TextAreaOnly)
508         {
509             if(searchRange == TextPointSearchRange.TextAreaOnly)
510             {
511                 if (p.Y < this.render.TextArea.TopLeft.Y ||
512                     p.Y > this.render.TextArea.BottomRight.Y)
513                     return TextPoint.Null;
514             }
515
516             TextPoint tp = new TextPoint();
517
518             if (this.LayoutLines.Count == 0)
519                 return tp;
520
521             //表示領域から探索を始めるのでパディングの分だけ引く
522             p.Y -= this.render.TextArea.Y;
523
524             //p.Y に最も近い行を調べる
525             var t = this.GetNearstRowAndOffsetY(this.Src.Row, p.Y);
526             double relX = 0, relY;
527             if (t == null)
528             {
529                 if(p.Y > 0)
530                 {
531                     tp.row = this.LayoutLines.Count - 1;
532                     relY = this.LayoutLines.GetLayout(tp.row).Height - this.render.emSize.Height;
533                 }
534                 else if(p.Y == 0)
535                 {
536                     tp.row = this.Src.Row;
537                     relY = 0;
538                 }
539                 else
540                 {
541                     tp.row = 0;
542                     relY = 0;
543                 }
544             }
545             else
546             {
547                 tp.row = t.Item1;
548                 relY = t.Item2;    //相対位置がマイナスなので反転させる
549             }
550
551             if (searchRange == TextPointSearchRange.TextAreaOnly)
552             {
553                 if (p.X < this.render.TextArea.X)
554                     return tp;
555             }
556
557             relX = p.X - this.render.TextArea.X;
558             tp.col = this.LayoutLines.GetLayout(tp.row).GetIndexFromPostion(relX,relY);
559
560             int lineLength = this.LayoutLines.GetLengthFromLineNumber(tp.row);
561             if (tp.col > lineLength)
562                 tp.col = lineLength;
563
564             return tp;
565         }
566
567         /// <summary>
568         /// TextPointに対応する座標を得る
569         /// </summary>
570         /// <param name="tp">レイアウトライン上の位置</param>
571         /// <returns>テキストエリアを左上とする相対位置</returns>
572         public Point GetPostionFromTextPoint(TextPoint tp)
573         {
574             Point p = new Point();
575             for (int i = this.Src.Row; i < tp.row; i++)
576             {
577                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
578                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
579                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
580                     continue;
581                 p.Y += this.LayoutLines.GetLayout(i).Height;
582             }
583             Point relP = this.LayoutLines.GetLayout(tp.row).GetPostionFromIndex(tp.col);
584             p.X += relP.X - Src.X + this.render.TextArea.X;
585             p.Y += this.render.TextArea.Y + relP.Y;
586             return p;
587         }
588
589         public Gripper HitGripperFromPoint(Point p)
590         {
591             if (this.Document.SelectGrippers.BottomLeft.IsHit(p))
592                 return this.Document.SelectGrippers.BottomLeft;
593             if (this.Document.SelectGrippers.BottomRight.IsHit(p))
594                 return this.Document.SelectGrippers.BottomRight;
595             return null;
596         }
597
598         public Rectangle GetRectFromIndex(int index,int width,int height)
599         {
600             TextPoint tp = this.LayoutLines.GetTextPointFromIndex(index);
601             return this.GetRectFromTextPoint(tp, width, height);
602         }
603
604         public Rectangle GetRectFromTextPoint(TextPoint tp, int width, int height)
605         {
606             if (tp.row < this.Src.Row)
607                 return Rectangle.Empty;
608             double radius = width / 2;
609             Point point = this.GetPostionFromTextPoint(tp);
610             double lineHeight = this.render.emSize.Height;
611             double srcOffsetY = this.Src.OffsetY; //画面上ではずれているので引く必要がある
612
613             Rectangle rect =  new Rectangle(point.X - radius, point.Y + lineHeight - srcOffsetY, width, height);
614
615             if (rect.BottomLeft.Y >= this.render.TextArea.BottomLeft.Y ||
616                 rect.BottomRight.X < this.render.TextArea.BottomLeft.X ||
617                 rect.BottomLeft.X > this.render.TextArea.BottomRight.X)
618                 return Rectangle.Empty;
619             return rect;
620         }
621
622         /// <summary>
623         /// キャレットを指定した位置に移動させる
624         /// </summary>
625         /// <param name="row"></param>
626         /// <param name="col"></param>
627         /// <param name="autoExpand">折り畳みを展開するなら真</param>
628         public void JumpCaret(int row, int col, bool autoExpand = true)
629         {
630             if (autoExpand)
631             {
632                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row);
633                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
634                 FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineHeadIndex, lineLength);
635                 if(foldingData != null)
636                 {
637                     if (this.LayoutLines.FoldingCollection.IsParentHidden(foldingData) || !foldingData.IsFirstLine(this.LayoutLines, row))
638                     {
639                         this.LayoutLines.FoldingCollection.Expand(foldingData);
640                     }
641                 }
642             }
643
644             //イベント呼び出しの再入防止のため
645             this.Document.SetCaretPostionWithoutEvent(new TextPoint(row, col));
646         }
647
648         /// <summary>
649         /// index上の文字が表示されるようにSrcを調整する
650         /// </summary>
651         /// <param name="index">インデックス</param>
652         /// <returns>調整されたら真。そうでなければ偽</returns>
653         public bool AdjustSrc(int index)
654         {
655             TextPoint startTextPoint = this.GetLayoutLineFromIndex(index);
656             Point relP = this.LayoutLines.GetLayout(startTextPoint.row).GetPostionFromIndex(startTextPoint.col);
657             if (relP.X < this.Src.X ||
658                 relP.X > this.Src.X + this.PageBound.Width)
659             {
660                 this.TryScroll(relP.X, this.Src.Row);
661                 return true;
662             }
663             if (startTextPoint.row < this.Src.Row ||
664                 startTextPoint.row > this.Src.Row + this.LineCountOnScreenWithInVisible)
665             {
666                 this.TryScroll(this.Src.X, startTextPoint.row, relP.Y);
667                 return true;
668             }
669             return false;
670         }
671
672         /// <summary>
673         /// キャレットがあるところまでスクロールする
674         /// </summary>
675         /// <return>再描写する必要があるなら真を返す</return>
676         /// <remarks>Document.Update(type == UpdateType.Clear)イベント時に呼び出した場合、例外が発生します</remarks>
677         public bool AdjustCaretAndSrc(AdjustFlow flow = AdjustFlow.Both)
678         {
679             IEditorRender render = (IEditorRender)base.render;
680
681             if (this.PageBound.Width == 0 || this.PageBound.Height == 0)
682             {
683                 this.SetCaretPostion(this.Padding.Left + render.FoldingWidth, 0);
684                 return false;
685             }
686
687             bool result = false;
688             TextPoint tp = this.Document.CaretPostion;
689             double x = this.CaretLocation.X;
690             double y = this.CaretLocation.Y;
691             Point relPoint = this.LayoutLines.GetLayout(tp.row).GetPostionFromIndex(tp.col);
692
693             if (flow == AdjustFlow.Col || flow == AdjustFlow.Both)
694             {
695                 x = relPoint.X;
696
697                 double left = this.Src.X;
698                 double right = this.Src.X + this.render.TextArea.Width;
699
700                 if (x >= left && x <= right)    //xは表示領域にないにある
701                 {
702                     x -= left;
703                 }
704                 else if (x > right) //xは表示領域の右側にある
705                 {
706                     this.Document.Src = new SrcPoint(x - this.render.TextArea.Width + this.ScrollMarginWidth,this.Document.Src.Row,this.Document.Src.OffsetY);
707                     if (this.Document.RightToLeft && this.Document.Src.X > 0)
708                     {
709                         System.Diagnostics.Debug.Assert(x > 0);
710                         this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Document.Src.OffsetY);
711                     }
712                     else
713                     {
714                         x = this.render.TextArea.Width - this.ScrollMarginWidth;
715                     }
716                     result = true;
717                 }
718                 else if (x < left)    //xは表示領域の左側にある
719                 {
720                     this.Document.Src = new SrcPoint(x - this.ScrollMarginWidth, this.Document.Src.Row, this.Document.Src.OffsetY);
721                     if (!this.Document.RightToLeft && this.Document.Src.X < this.render.TextArea.X)
722                     {
723                         this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Document.Src.OffsetY);
724                     }
725                     else
726                     {
727                         x = this.ScrollMarginWidth;
728                     }
729                     result = true;
730                 }
731                 x += this.render.TextArea.X;
732             }
733
734             if (flow == AdjustFlow.Row || flow == AdjustFlow.Both)
735             {
736                 int PhyLineCountOnScreen = (int)(this.render.TextArea.Height / this.render.emSize.Height);
737                 //計算量を減らすため
738                 if (tp.row < this.Src.Row || this.Src.Row + PhyLineCountOnScreen * 2 < tp.row)
739                     this.Document.Src = new SrcPoint(this.Src.X, tp.row, -relPoint.Y);
740
741                 //キャレットのY座標を求める
742                 double lineHeight = this.render.emSize.Height;
743                 double caret_y = this.Src.OffsetY;  //src.rowからキャレット位置
744                 double alignedHeight = PhyLineCountOnScreen * this.render.emSize.Height;
745                 for (int i = this.Src.Row; i < tp.row; i++)
746                 {
747                     int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
748                     int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
749
750                     if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
751                         continue;
752                     caret_y += this.LayoutLines.GetLayout(i).Height;
753                 }
754                 caret_y += relPoint.Y;
755
756                 if (caret_y < 0)
757                 {
758                     this.Document.Src = new SrcPoint(this.Src.X, tp.row, -relPoint.Y);
759                     y = 0;
760                 }
761                 else if (caret_y >= 0 && caret_y < alignedHeight)
762                 {
763                     y = caret_y;
764                 }
765                 else if(caret_y >= alignedHeight)
766                 {
767                     double caretYFromTextArea = Math.Min(caret_y - alignedHeight, alignedHeight - lineHeight);
768                     var newsrc = this.GetNearstRowAndOffsetY(tp.row, -caretYFromTextArea);
769                     if(newsrc == null)
770                         this.Document.Src = new SrcPoint(this.Src.X, tp.row, 0);
771                     else
772                         this.Document.Src = new SrcPoint(this.Src.X, newsrc.Item1, -newsrc.Item2);
773                     y = caretYFromTextArea;
774                 }
775                 y += this.render.TextArea.Y;
776                 result = true;
777             }
778
779             this.SetCaretPostion(x, y);
780
781             if (result)
782             {
783                 this.OnSrcChanged(null);
784             }
785
786             return result;
787         }
788
789         /// <summary>
790         /// レイアウト行をテキストポイントからインデックスに変換する
791         /// </summary>
792         /// <param name="tp">テキストポイント表す</param>
793         /// <returns>インデックスを返す</returns>
794         public int GetIndexFromLayoutLine(TextPoint tp)
795         {
796             return this.LayoutLines.GetIndexFromTextPoint(tp);
797         }
798
799         /// <summary>
800         /// インデックスからレイアウト行を指し示すテキストポイントに変換する
801         /// </summary>
802         /// <param name="index">インデックスを表す</param>
803         /// <returns>テキストポイント返す</returns>
804         public TextPoint GetLayoutLineFromIndex(int index)
805         {
806             return this.LayoutLines.GetTextPointFromIndex(index);
807         }
808
809         /// <summary>
810         /// 指定した座標までスクロールする
811         /// </summary>
812         /// <param name="x"></param>
813         /// <param name="row"></param>
814         /// <remarks>
815         /// 範囲外の座標を指定した場合、範囲内に収まるように調整されます
816         /// </remarks>
817         public void Scroll(double x, int row)
818         {
819             if (x < 0)
820                 x = 0;
821             if(row < 0)
822                 row = 0;
823             int endRow = this.LayoutLines.Count - 1 - this.LineCountOnScreen;
824             if (endRow < 0)
825                 endRow = 0;
826             if (row > endRow)
827                 row = endRow;
828             base.TryScroll(x, row);
829         }
830
831         /// <summary>
832         /// 指定行までスクロールする
833         /// </summary>
834         /// <param name="row">行</param>
835         /// <param name="alignTop">指定行を画面上に置くなら真。そうでないなら偽</param>
836         public void ScrollIntoView(int row, bool alignTop)
837         {
838             this.Scroll(0, row);
839             if (alignTop)
840                 return;
841             double y = this.render.TextArea.Height;
842             for (int i = row; i >= 0; i--)
843             {
844                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
845                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
846                 double height = this.LayoutLines.GetLayout(i).Height;
847                 if (y - height <= 0)
848                 {
849                     this.Scroll(0, i);
850                 }
851                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
852                     continue;
853                 y -= height;
854             }
855         }
856
857         public int AdjustRow(int row, bool isMoveNext)
858         {
859             if (this.LayoutLines.FoldingStrategy == null)
860                 return row;
861             int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row);
862             int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
863             FoldingItem foldingData = this.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength);
864             if (foldingData != null && !foldingData.Expand)
865             {
866                 if (foldingData.End == this.Document.Length)
867                     return row;
868                 if (isMoveNext && lineHeadIndex > foldingData.Start)
869                     row = this.LayoutLines.GetLineNumberFromIndex(foldingData.End) + 1;
870                 else
871                     row = this.LayoutLines.GetLineNumberFromIndex(foldingData.Start);
872                 if(row > this.LayoutLines.Count - 1)
873                     row = this.LayoutLines.GetLineNumberFromIndex(foldingData.Start);
874             }
875             return row;
876         }
877
878         protected override void CalculateClipRect()
879         {
880             IEditorRender render = (IEditorRender)base.render;
881             double x, y, width, height;
882
883             if (this.Document.DrawLineNumber)
884             {
885                 if (this.Document.RightToLeft)
886                     x = this.Padding.Left;
887                 else
888                     x = this.Padding.Left + UpdateAreaTotalWidth + this.render.LineNemberWidth + this.LineNumberMargin + render.FoldingWidth;
889                 width = this.PageBound.Width - this.render.LineNemberWidth - this.LineNumberMargin - this.Padding.Left - this.Padding.Right - render.FoldingWidth - UpdateAreaTotalWidth;
890             }
891             else
892             {
893                 if (this.Document.RightToLeft)
894                     x = this.Padding.Left;
895                 else
896                     x = this.Padding.Left + UpdateAreaTotalWidth + render.FoldingWidth;
897                 width = this.PageBound.Width - this.Padding.Left - this.Padding.Right - render.FoldingWidth - UpdateAreaTotalWidth;
898             }
899
900             y = this.Padding.Top;
901             height = this.PageBound.Height - this.Padding.Top - this.Padding.Bottom;
902
903             if (this.Document.HideRuler == false)
904             {
905                 double rulerHeight = this.render.emSize.Height + LineMarkerThickness;
906                 y += rulerHeight;
907                 height -= rulerHeight;
908             }
909
910             if (width < 0)
911                 width = 0;
912
913             if (height < 0)
914                 height = 0;
915
916             this.render.TextArea = new Rectangle(x, y, width, height);
917
918             this.LineBreakingMarginWidth = width * 5 / 100;
919         }
920
921         public override void CalculateLineCountOnScreen()
922         {
923             if (this.LayoutLines.Count == 0 || this.PageBound.Height == 0)
924                 return;
925
926             double y = 0;
927             int i = this.Src.Row;
928             int visualCount = this.Src.Row;
929             for (; true; i++)
930             {
931                 int row = i < this.LayoutLines.Count ? i : this.LayoutLines.Count - 1;
932
933                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row);
934                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(row);
935
936                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex) && row < this.LayoutLines.Count - 1)
937                     continue;
938
939                 ITextLayout layout = this.LayoutLines.GetLayout(row);
940
941                 double width = layout.Width;
942
943                 if (width > this._LongestWidth)
944                     this._LongestWidth = width;
945
946                 double lineHeight = layout.Height;
947
948                 y += lineHeight;
949
950                 if (y >= this.render.TextArea.Height)
951                     break;
952                 visualCount++;
953             }
954             this.LineCountOnScreen = Math.Max(visualCount - this.Src.Row - 1, 0);
955             this.LineCountOnScreenWithInVisible = Math.Max(i - this.Src.Row - 1, 0);
956         }
957
958         public override void CalculateWhloeViewPort()
959         {
960             if(this.Document.LineBreak == LineBreakMethod.None)
961             {
962                 this._LongestHeight = this.LayoutLines.Count * this.render.emSize.Height;
963                 //CalculateLineCountOnScreenで最小幅は計算する
964                 return;
965             }
966             double width = 0, height = 0;
967             for(int i = 0; i < this.LayoutLines.Count; i++)
968             {
969                 int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
970                 int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
971
972                 if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex) && i < this.LayoutLines.Count - 1)
973                     continue;
974
975                 ITextLayout layout = this.LayoutLines.GetLayout(i);
976                 if (width > this._LongestWidth)
977                     this._LongestWidth = width;
978                 height += layout.Height;
979             }
980         }
981
982         void SetCaretPostion(double x, double y)
983         {
984             this.CaretLocation = new Point(x + this.PageBound.X, y + this.PageBound.Y);
985         }
986
987         void FoldingCollection_StatusChanged(object sender, FoldingItemStatusChangedEventArgs e)
988         {
989             this.CalculateLineCountOnScreen();
990         }
991
992         enum AreaType
993         {
994             UpdateArea,
995             FoldingArea,
996             LineNumberArea,
997             TextArea
998         }
999
1000         double GetRealtiveX(AreaType type)
1001         {
1002             IEditorRender render = (IEditorRender)base.render;
1003             switch (type)
1004             {
1005                 case AreaType.UpdateArea:
1006                     if (this.Document.RightToLeft)
1007                         return this.PageBound.TopRight.X - UpdateAreaTotalWidth;
1008                     if (this.Document.DrawLineNumber)
1009                         return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin - render.FoldingWidth - UpdateAreaTotalWidth;
1010                     else
1011                         return this.render.TextArea.X - render.FoldingWidth - UpdateAreaTotalWidth;
1012                 case AreaType.FoldingArea:
1013                     if (this.Document.RightToLeft)
1014                         return this.PageBound.TopRight.X - render.FoldingWidth;
1015                     if (this.Document.DrawLineNumber)
1016                         return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin - render.FoldingWidth;
1017                     else
1018                         return this.render.TextArea.X - render.FoldingWidth;
1019                 case AreaType.LineNumberArea:
1020                     if (this.Document.DrawLineNumber == false)
1021                         throw new InvalidOperationException();
1022                     if (this.Document.RightToLeft)
1023                         return this.PageBound.TopRight.X - UpdateAreaTotalWidth - render.FoldingWidth - this.render.LineNemberWidth;
1024                     else
1025                         return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin;
1026                 case AreaType.TextArea:
1027                     return this.render.TextArea.X;
1028             }
1029             throw new ArgumentOutOfRangeException();
1030         }
1031     }
1032 }